dro 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dro-0.0.1/LICENSE ADDED
File without changes
dro-0.0.1/MANIFEST.in ADDED
@@ -0,0 +1 @@
1
+ include pydro/README.md
dro-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.1
2
+ Name: dro
3
+ Version: 0.0.1
4
+ Summary: A package of distributionally robust optimization (DRO) methods. Implemented via cvxpy and PyTorch
5
+ Home-page: https://github.com/namkoong-lab/dro
6
+ Author: Jiashuo Liu, Tianyu Wang, Peng Cui, Hongseok Namkoong
7
+ Author-email: liujiashuo77@gmail.com, tw2837@columbia.edu, cuip@tsinghua.edu.cn, namkoong@gsb.columbia.edu
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: MacOS
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Requires-Python: >=3
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+
16
+ ### DRO Package
17
+
18
+ > <a href="https://ljsthu.github.io">Jiashuo Liu*</a>, <a href="https://wangtianyu61.github.io">Tianyu Wang*</a>, <a href="https://pengcui.thumedialab.com">Peng Cui</a>, <a href="https://hsnamkoong.github.io">Hongseok Namkoong</a>
19
+
20
+ > Tsinghua University, Columbia University
21
+
22
+
23
+ `DRO` is a python package that implements 12 typical DRO methods on linear models (SVM, logistic regression, and linear regression). It is built based on `cvxpy`. Implemented DRO methods include:
24
+ * $f$-DRO
25
+ * CVaR-DRO
26
+ * KL-DRO
27
+ * TV-DRO
28
+ * Marginal DRO (CVaR)
29
+ * Wasserstein DRO
30
+ * Wasserstein DRO
31
+ * Augmented Wasserstein DRO
32
+ * Regularized Wasserstein DRO
33
+ * MMD-DRO
34
+ * Sinkhorn-DRO
35
+ * Holistic DRO
36
+ * Unified-DRO
37
+ * $L_2$ cost
38
+ * $L_{inf}$ cost
39
+
40
+ Current version only contains linear models. And further version will incorporate neural network implementations via some approximations.
@@ -0,0 +1,7 @@
1
+ # Built on Folktables and the codebase https://github.com/jpgard/subgroup-robustness-grows-on-trees
2
+ __version__ = "0.0.1"
3
+ __author__ = 'Jiashuo Liu, Tianyu Wang, Peng Cui, Hongseok Namkoong'
4
+ __credits__ = 'Peng Cui\'s Group and Hongseok Namkoong\' Group'
5
+
6
+
7
+ from .fetch_method import fetch_method
@@ -0,0 +1,38 @@
1
+ from .src import *
2
+
3
+
4
+ def fetch_method(method, is_regression, input_dim=77):
5
+ if method == 'unified_dro_l2':
6
+ if is_regression == 1 or is_regression == 2:
7
+ raise NotImplementedError("Unified DRO does not support regression!")
8
+ else:
9
+ return MOT_Robust_CLF_L2()
10
+ if method == 'unified_dro_linf':
11
+ if is_regression == 1 or is_regression == 2:
12
+ raise NotImplementedError("Unified DRO does not support regression!")
13
+ else:
14
+ return MOT_Robust_CLF_Linf()
15
+ elif method == 'hr_dro_lr':
16
+ return HR_DRO_LR(is_regression=is_regression)
17
+ elif method == 'chi2_dro':
18
+ return chi2_DRO(input_dim=input_dim, is_regression=is_regression)
19
+ elif method == 'kl_dro':
20
+ return KL_DRO(input_dim=input_dim, is_regression=is_regression)
21
+ elif method == 'tv_dro':
22
+ return TV_DRO(input_dim=input_dim, is_regression=is_regression)
23
+ elif method == 'marginal_cvar_dro':
24
+ return Marginal_CVaR_DRO(input_dim=input_dim, is_regression=is_regression)
25
+ elif method == 'cvar_dro':
26
+ return CVaR_DRO(input_dim=input_dim, is_regression=is_regression)
27
+ elif method == 'wasserstein_dro':
28
+ return Wasserstein_DRO(input_dim=input_dim, is_regression=is_regression)
29
+ elif method == 'wasserstein_dro_satisficing':
30
+ return Wasserstein_DRO_satisficing(input_dim=input_dim, is_regression=is_regression)
31
+ elif method == 'wasserstein_dro_aug':
32
+ return Wasserstein_DRO_aug(input_dim=input_dim, is_regression=is_regression)
33
+ elif method == 'sinkhorn_dro':
34
+ return Sinkhorn_DRO_Linear(input_dim=input_dim, is_regression=is_regression)
35
+ elif method == 'mmd_dro':
36
+ return MMD_DRO(input_dim=input_dim, is_regression=is_regression)
37
+ else:
38
+ raise NotImplementedError(f"Algorithm {method} not implemented yet!")
@@ -0,0 +1,111 @@
1
+ import cvxpy as cp
2
+ import numpy as np
3
+ from sklearn.metrics import f1_score
4
+
5
+ class HR_DRO_LR:
6
+ def __init__(self, r=1.0, alpha=1.0, epsilon=0.5, epsilon_prime=1.0, is_regression=0):
7
+ self.r = r
8
+ self.alpha = alpha
9
+ self.epsilon = epsilon
10
+ self.epsilon_prime = epsilon_prime
11
+ self.is_regression = is_regression
12
+
13
+ def update(self, config={}):
14
+ if 'r' in config.keys():
15
+ self.r = config["r"]
16
+ if 'alpha' in config.keys():
17
+ self.alpha = config["alpha"]
18
+ if 'epsilon' in config.keys():
19
+ self.epsilon = config["epsilon"]
20
+ if 'epsilon_prime' in config.keys():
21
+ self.epsilon_prime = config["epsilon_prime"]
22
+
23
+ def fit(self, X, Y):
24
+ T = X.shape[0]
25
+ theta = cp.Variable(X.shape[1]) # Define the size based on the problem
26
+ w = cp.Variable(T)
27
+ lambda_ = cp.Variable(nonneg=True)
28
+ beta = cp.Variable(nonneg=True)
29
+ eta = cp.Variable()
30
+ temp = cp.Variable()
31
+
32
+ # Objective
33
+ objective = cp.Minimize(1/T * cp.sum(w) + lambda_ * (self.r - 1) + beta * self.alpha + eta)
34
+
35
+ if self.is_regression == 1 or self.is_regression == 2:
36
+ # Constraints
37
+ constraints = []
38
+ # Add constraints based on the problem
39
+ for t in range(T):
40
+ constraints.append(temp >= cp.abs(theta.T @ X[t] - Y[t]))
41
+ constraints.append(w[t]>= cp.rel_entr(lambda_, (eta - cp.abs(theta.T @ X[t] - Y[t]) - self.epsilon * cp.norm(theta, 2)) ))
42
+ constraints.append(w[t]>= cp.rel_entr(lambda_, (eta - temp - self.epsilon_prime * cp.norm(theta, 2)))-beta)
43
+ constraints.append(eta >= cp.abs(theta.T @ X[t] - Y[t]) + self.epsilon_prime * cp.norm(theta, 2))
44
+ elif self.is_regression == 0:
45
+ Y = 2*Y - 1.0
46
+ # Constraints
47
+ constraints = []
48
+ # Add constraints based on the problem
49
+ constraints.append(eta>=1e-6)
50
+ for t in range(T):
51
+ constraints.append(temp <= Y[t]*(theta.T@X[t]))
52
+ constraints.append(w[t] >= cp.rel_entr(lambda_, eta))
53
+ constraints.append(w[t]>= cp.rel_entr(lambda_, (eta - 1 + Y[t]*(theta.T @ X[t])-self.epsilon*cp.norm(theta,2)) ))
54
+ constraints.append(w[t]>= cp.rel_entr(lambda_, (eta - 1 + temp - self.epsilon_prime * cp.norm(theta, 2)))-beta)
55
+ constraints.append(eta >= 1-Y[t]*(theta.T @ X[t])+self.epsilon*cp.norm(theta,2))
56
+ else:
57
+ raise NotImplementedError
58
+
59
+ # Problem
60
+ prob = cp.Problem(objective, constraints)
61
+
62
+ # Solve
63
+ prob.solve(solver=cp.MOSEK,
64
+ mosek_params={"MSK_DPAR_INTPNT_CO_TOL_REL_GAP": 1e-8},
65
+ verbose=True)
66
+
67
+
68
+ self.theta = theta.value
69
+ self.w = w.value
70
+ self.lambda_ = lambda_.value
71
+ self.beta = beta.value
72
+ self.eta = eta.value
73
+
74
+ model_params = {}
75
+ model_params["theta"] = self.theta.reshape(-1).tolist()
76
+ return model_params
77
+
78
+ def predict(self, X):
79
+ scores = self.theta.T @ X.T
80
+ preds = scores.copy()
81
+ preds[scores >= 0] = 1
82
+ preds[scores < 0] = 0
83
+ return preds
84
+
85
+ def score(self, X, y):
86
+ # calculate accuracy of the given test data set
87
+ predictions = self.predict(X)
88
+ acc = np.mean([predictions.flatten() == y.flatten()])
89
+ f1 = f1_score(y, predictions, average='macro')
90
+ return acc, f1
91
+
92
+
93
+
94
+
95
+
96
+ if __name__=="__main__":
97
+ from sklearn.datasets import make_regression
98
+ from sklearn.linear_model import LinearRegression
99
+ import numpy as np
100
+
101
+ sample_size = 1000
102
+ feature_size = 10
103
+ X, y = make_regression(n_samples = sample_size, n_features = feature_size, noise = 1, random_state = 42)
104
+
105
+ method = HR_DRO_LR()
106
+ method.fit(X,y)
107
+ print(method.theta)
108
+
109
+ model = LinearRegression(fit_intercept=True)
110
+ model.fit(X, y)
111
+ print(model.coef_)
@@ -0,0 +1,116 @@
1
+ from .base import *
2
+ import numpy as np
3
+ import math
4
+ import cvxpy as cp
5
+ from sklearn.metrics.pairwise import rbf_kernel
6
+ from sklearn.metrics import euclidean_distances
7
+
8
+ """
9
+ we set (X, Y) as the same scale but it may not be in practice.
10
+ """
11
+
12
+
13
+ class MMD_DRO(base_DRO):
14
+ def __init__(self, input_dim, is_regression=2):
15
+ base_DRO.__init__(self, input_dim, is_regression)
16
+ self.eta = 0.1
17
+ self.sampling_method = 'bound'
18
+ self.n_certify_ratio = 1
19
+ def update(self, config = {}):
20
+ if 'eta' in config.keys():
21
+ self.eta = config['eta']
22
+ if 'sampling_method' in config.keys():
23
+ assert (config['sampling_method'] in ['bound', 'hull'])
24
+ self.sampling_method = config['sampling_method']
25
+ if 'n_certify_ratio' in config.keys():
26
+ self.n_certify_ratio = config['n_certify_ratio']
27
+
28
+ def matrix_decomp(self, K):
29
+ try:
30
+ L = np.linalg.cholesky(K)
31
+ except:
32
+ # print('warning, K is singular')
33
+ d, v = np.linalg.eigh(K) #L == U*diag(d)*U'. the scipy function forces real eigs
34
+ d[np.where(d < 0)] = 0 # get rid of small eigs
35
+ L = v @ np.diag(np.sqrt(d))
36
+ return L
37
+
38
+ def medium_heuristic(self, X, Y):
39
+ if self.is_regression == 1 or self.is_regression == 2:
40
+ distsqr = euclidean_distances(X, Y, squared = True)
41
+ else:
42
+ distsqr = euclidean_distances(X[:,0:-1], Y[:,0:-1], squared = True)
43
+
44
+ kernel_width = np.sqrt(0.5 * np.median(distsqr))
45
+
46
+ # in sklearn,
47
+ # kernel is done by K(x, y) = exp(-gamma ||x-y||^2)
48
+ kernel_gamma = 1.0 / (2 * kernel_width ** 2)
49
+
50
+ return kernel_width, kernel_gamma
51
+
52
+ def cvx_loss(self, theta, zeta):
53
+ if self.is_regression == 1 or self.is_regression == 2:
54
+ loss = (zeta[-1] - theta @ zeta[:-1]) ** 2
55
+ else:
56
+ loss = cp.pos(1 - cp.multiply(zeta[-1], theta @ zeta[:-1]))
57
+ return loss
58
+
59
+ def fit(self, X, y):
60
+ if self.is_regression == 0:
61
+ y = 2*y-1
62
+ sample_size, __ = X.shape
63
+ n_certify = int(self.n_certify_ratio * sample_size)
64
+
65
+ theta = cp.Variable(self.input_dim) # variable \theta
66
+
67
+ # constraint on the decision variable
68
+
69
+ # KDRO part
70
+ a = cp.Variable(sample_size + n_certify) # variable \alpha
71
+ f0 = cp.Variable() # variable f0
72
+
73
+ # --------------------------------------------------------------------------------
74
+ # Step 1: generate the sampled support
75
+ # --------------------------------------------------------------------------------
76
+ if self.sampling_method == 'bound': # sample within certain bound
77
+ # let the samples also live in the intervel I sampled uncertainty w
78
+ zeta = np.random.uniform(-1, 1, size=[n_certify, self.input_dim + 1])
79
+ elif self.sampling_method == 'hull': # sample using convex hull of empirical data
80
+ # let the samples also live in the intervel I sampled uncertainty w
81
+ # this is equiv. to really do it in multi dimensions, need to sample coeff. from a simplex
82
+ if self.is_regression == 1 or self.is_regression == 2:
83
+ zeta1 = np.random.uniform(np.min(X), np.max(X), size = [n_certify, self.input_dim])
84
+ zeta2 = np.random.uniform(np.min(y), np.max(y), size = [n_certify, 1])
85
+ else:
86
+ zeta1 = np.random.uniform(-1, 1, size = [n_certify, self.input_dim])
87
+ zeta2 = np.random.choice([-1, 1], size = (n_certify, 1))
88
+ zeta = np.concatenate([zeta1, zeta2], axis = 1)
89
+ else:
90
+ raise NotImplementedError
91
+
92
+ data = np.concatenate([X, y.reshape(-1, 1)], axis = 1)
93
+ # in practice, we always include the empirical data in the sampled support
94
+ zeta = np.concatenate([data, zeta])
95
+
96
+ kernel_width, kernel_gamma = self.medium_heuristic(zeta, zeta)
97
+
98
+ # --------------------------------------------------------------------------------
99
+ # Step 3: setup objective function and constraints
100
+ # --------------------------------------------------------------------------------
101
+ # evaluate the f, K at the value of zetas
102
+ K = rbf_kernel(zeta, zeta)
103
+ #gamma = kernel_gamma)
104
+ f = a @ K
105
+ constr = []
106
+ for i in range((len(zeta))):
107
+ constr += [self.cvx_loss(theta, zeta[i]) <= f0 + f[i]]
108
+
109
+ obj = f0 + cp.sum(f[0:sample_size]) / sample_size + self.eta * cp.norm(a.T @ self.matrix_decomp(K))
110
+ opt = cp.Problem(cp.Minimize(obj), constr)
111
+
112
+
113
+ opt.solve(solver = cp.MOSEK)
114
+ self.theta = theta.value #, obj.value, a.value, f0.value, kernel_gamma, zeta
115
+
116
+
@@ -0,0 +1,277 @@
1
+ import numpy as np
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.nn.functional as F
5
+ import torch.optim as optim
6
+ from torch.utils.data import TensorDataset, DataLoader
7
+ from torch.autograd import Variable
8
+ import math
9
+ from sklearn.metrics import f1_score
10
+
11
+ class LinearModel(nn.Module):
12
+ def __init__(self, input_dim, output_dim):
13
+ super(LinearModel, self).__init__()
14
+ self.linear = nn.Linear(input_dim, output_dim, bias=True)
15
+
16
+ def forward(self, x):
17
+ return self.linear(x)
18
+
19
+
20
+ def to_tensor(x):
21
+ if type(x) == np.ndarray:
22
+ return torch.from_numpy(x).float()
23
+ elif type(x) == torch.Tensor:
24
+ return x
25
+ else:
26
+ print("Type error. Input should be either numpy array or torch tensor")
27
+
28
+ def SDRO_eval(theta, Lambda, Reg, X, Y):
29
+ n,d = X.shape
30
+
31
+ ratio_1 = Lambda / (Lambda - 2 * np.linalg.norm(theta)**2)
32
+ residual = np.mean((X@theta - Y)**2,0)
33
+ obj_1 = ratio_1 * residual
34
+ obj_2 = Lambda*Reg/2 * np.linalg.slogdet(np.eye(d) - theta@theta.T*2/Lambda)[1]
35
+ return obj_1[0] - obj_2, np.sqrt(residual[0])
36
+
37
+
38
+
39
+
40
+ class Sinkhorn_DRO_Linear:
41
+ def __init__(self, input_dim, reg_=1, lambda_=1, output_dim=1,
42
+ maxiter = 50, learning_rate = 1e-2, K_sample_max=5, is_regression=0):
43
+ print(input_dim)
44
+ self.model = LinearModel(input_dim, output_dim)
45
+ self.lambda_ = lambda_
46
+ self.reg_ = reg_
47
+ self.maxiter_ = maxiter
48
+ self.learning_rate = learning_rate
49
+ self.K_sample_max = K_sample_max
50
+ self.is_regression = is_regression
51
+
52
+ def update(self, config={}):
53
+ if "reg" in config.keys():
54
+ self.reg_ = config["reg"]
55
+ if "lambda" in config.keys():
56
+ self.lambda_ = config["lambda"]
57
+ if "k" in config.keys():
58
+ self.K_sample_max = config["k"]
59
+
60
+ def predict(self, X):
61
+ X = torch.tensor(X).float()
62
+ self.model.cpu()
63
+ if self.is_regression:
64
+ pred = self.model(X)
65
+ else:
66
+ pred = self.model(X)
67
+ pred[pred > 0.5] = 1
68
+ pred[pred <= 0.5] = 0
69
+
70
+ return pred.detach().numpy()
71
+
72
+ def score(self, X, y):
73
+ if self.is_regression:
74
+ return np.mean(self.predict(X).reshape(-1)-y.reshape(-1))
75
+ else:
76
+ acc = np.mean([self.predict(X).flatten() == y.flatten()])
77
+ f1 = f1_score(y, self.predict(X), average='macro')
78
+ return acc, f1
79
+
80
+ def fit(self, X, y, optimization_type="SG"):
81
+ X_tensor = torch.tensor(X, dtype=torch.float32)
82
+ Y_tensor = torch.tensor(y, dtype=torch.float32)
83
+ dataset = TensorDataset(X_tensor, Y_tensor)
84
+ dataloader = DataLoader(dataset, batch_size=64, shuffle=True)
85
+
86
+ if optimization_type == 'SG':
87
+ self.SDRO_SG_solver(dataloader)
88
+ elif optimization_type == 'MLMC':
89
+ self.SDRO_MLMC_solver(dataloader)
90
+ elif optimization_type == 'RTMLMC':
91
+ self.SDRO_RTMLMC_solver(dataloader)
92
+ else:
93
+ raise ImplementationError("This optimization method is not implemented! Please choose one in \{SG, MLMC, RTMLMC\}")
94
+ pass
95
+
96
+ theta = self.model.linear.weight.cpu().detach().tolist()
97
+ bias = self.model.linear.bias.cpu().detach().tolist()
98
+
99
+ params = {}
100
+ params['theta'] = theta
101
+ params['bias'] = bias
102
+ return params
103
+
104
+
105
+ def SDRO_SG_solver(self, dataloader, device=torch.device("cuda:7")):
106
+ """
107
+ 2-SDRO Approach with SG estimator for Regression Problem
108
+ # Input:
109
+ # Feature: N samples of R^d [dim: N*d]
110
+ # Target: labels of N samples [dim: N*1]
111
+ # theta: initial guess for optimization
112
+ # Lambda: Lagrangian multiplier
113
+ # Reg: bandwidth
114
+ # Output:
115
+ # theta: optimized decision
116
+ """
117
+ iter = 0
118
+ Lambda_Reg = self.lambda_ * self.reg_
119
+ self.model.to(device)
120
+ optimizer_theta = torch.optim.Adam(self.model.parameters(), lr=self.learning_rate)
121
+
122
+
123
+ for epoch in range(self.maxiter_):
124
+ for _, (data, target) in enumerate(dataloader):
125
+ iter = iter + 1
126
+
127
+ # generate stochastic samples
128
+ N, d = data.shape
129
+ data, target = Variable(data), Variable(target)
130
+ m = int(2**self.K_sample_max)
131
+
132
+ optimizer_theta.zero_grad()
133
+ data_noise = torch.randn([m, N, d]) * np.sqrt(self.reg_) + data.reshape([1,N,d])
134
+ data_noise_vec = data_noise.reshape([-1,d])
135
+ target_noise = target.repeat(m,1).reshape(-1,1).to(device)
136
+
137
+ haty = self.model(data_noise_vec.to(device))
138
+
139
+ obj_vec = (haty - target_noise) ** 2
140
+ obj_mat = obj_vec.reshape([m, N])
141
+ Residual = obj_mat / Lambda_Reg
142
+
143
+ Loss_SDRO = (torch.logsumexp(Residual, dim=0, keepdim=True)-math.log(m)) * Lambda_Reg
144
+ Loss_SDRO_avg = torch.mean(Loss_SDRO)
145
+
146
+ Loss_SDRO_avg.backward()
147
+ optimizer_theta.step()
148
+
149
+ if iter % 10000 == 0:
150
+ print(f"Iter {iter} {Loss_SDRO_avg.data}")
151
+
152
+
153
+ def SDRO_MLMC_solver(self, dataloader):
154
+ iter = 0
155
+ Lambda_Reg = self.lambda_ * self.reg_
156
+
157
+ optimizer_theta = torch.optim.SGD(self.model.parameters(), lr=self.learning_rate)
158
+ N_ell_hist = np.int_(2**(np.arange(self.K_sample_max) + 1))
159
+
160
+
161
+ for epoch in range(self.maxiter_):
162
+ for _, (data, target) in enumerate(dataloader):
163
+ iter = iter + 1
164
+ # generate stochastic samples
165
+ N, d = data.shape
166
+ data, target = Variable(data), Variable(target)
167
+ optimizer_theta.zero_grad()
168
+
169
+ m_total = 0
170
+ for K_sample in np.arange(self.K_sample_max):
171
+ m = int(2**K_sample)
172
+ N_ell = N_ell_hist[-K_sample-1]
173
+ data_ell = data[:N_ell, :]
174
+ N_ell, d = data_ell.shape
175
+ target_ell = target[:N_ell]
176
+
177
+ data_noise = torch.randn([m, N_ell, d]) * np.sqrt(self.reg_) + data_ell.reshape([1,N_ell,d])
178
+ m_total += m * N_ell
179
+ data_noise_vec = data_noise.reshape([-1,d])
180
+ target_noise = target_ell.repeat(m,1)
181
+
182
+ haty = self.model(data_noise_vec)
183
+ obj_vec = (haty - target_noise) ** 2
184
+ obj_mat = obj_vec.reshape([m, N_ell])
185
+ Residual = obj_mat / Lambda_Reg
186
+
187
+ if K_sample == 0:
188
+ Loss_SDRO = torch.log(torch.mean(torch.exp(Residual), dim=0)) * Lambda_Reg
189
+ Loss_SDRO_avg_K_sample = torch.mean(Loss_SDRO)
190
+ Loss_SDRO_avg_sum = Loss_SDRO_avg_K_sample
191
+ else:
192
+ m1 = int(m/2)
193
+ Residual_half = Residual[:m1,:]
194
+ Residual_remain = Residual[m1:,:]
195
+ Loss_SDRO_1 = torch.log(torch.mean(torch.exp(Residual), dim=0)) * Lambda_Reg
196
+ Loss_SDRO_avg_1 = torch.mean(Loss_SDRO_1)
197
+ Loss_SDRO_2 = (torch.log(torch.mean(torch.exp(Residual_half), dim=0)) + torch.log(torch.mean(torch.exp(Residual_remain), dim=0))) * Lambda_Reg
198
+ Loss_SDRO_avg_2 = torch.mean(Loss_SDRO_2)
199
+ Loss_SDRO_avg_K_sample = Loss_SDRO_avg_1 - 0.5 * Loss_SDRO_avg_2
200
+ Loss_SDRO_avg_sum = Loss_SDRO_avg_sum + Loss_SDRO_avg_K_sample
201
+
202
+ Loss_SDRO_avg_sum.backward()
203
+ optimizer_theta.step()
204
+
205
+ # if torch.linalg.norm(self.model.linear.weight.data) > 0.95*np.sqrt(Lambda/2):
206
+ # self.model.linear.weight.data = self.model.linear.weight.data / torch.linalg.norm(self.model.linear.weight.data) * 0.95*np.sqrt(Lambda/2)
207
+
208
+
209
+
210
+ def SDRO_RTMLMC_solver(self, dataloader):
211
+ iter = 0
212
+ Lambda_Reg = self.lambda_ * self.reg_
213
+ optimizer_theta = torch.optim.SGD(self.model.parameters(), lr=self.learning_rate)
214
+
215
+ # sampling from truncated gemoetric distribution
216
+ elements = np.arange(self.K_sample_max)
217
+ probabilities = (0.5) ** (elements)
218
+ probabilities = probabilities / np.sum(probabilities)
219
+
220
+ for epoch in range(self.maxiter_):
221
+ for _, (data, target) in enumerate(dataloader):
222
+ iter = iter + 1
223
+ # generate stochastic samples
224
+ N, d = data.shape
225
+ data, target = Variable(data), Variable(target)
226
+
227
+ K_sample = int(np.random.choice(list(elements), 1, list(probabilities)))
228
+ m = int(2**K_sample)
229
+ optimizer_theta.zero_grad()
230
+ data_noise = torch.randn([m, N, d]) * np.sqrt(Reg) + data.reshape([1,N,d])
231
+ data_noise_vec = data_noise.reshape([-1,d])
232
+ target_noise = target.repeat(m,1)
233
+
234
+ haty = data_noise_vec @ theta_torch.type(torch.float64)
235
+ obj_vec = (haty - target_noise) ** 2
236
+ obj_mat = obj_vec.reshape([m, N])
237
+ Residual = obj_mat / Lambda_Reg
238
+
239
+ if m == 1:
240
+ Loss_SDRO = torch.log(torch.mean(torch.exp(Residual), dim=0)) * Lambda_Reg
241
+ Loss_SDRO_avg = torch.mean(Loss_SDRO)
242
+ else:
243
+ m1 = int(2**(K_sample-1))
244
+ Residual_half = Residual[:m1,:]
245
+ Residual_remain = Residual[m1:,:]
246
+ Loss_SDRO_1 = torch.log(torch.mean(torch.exp(Residual), dim=0)) * Lambda_Reg
247
+ Loss_SDRO_avg_1 = torch.mean(Loss_SDRO_1)
248
+ Loss_SDRO_2 = (torch.log(torch.mean(torch.exp(Residual_half), dim=0)) + torch.log(torch.mean(torch.exp(Residual_remain), dim=0))) * Lambda_Reg
249
+ Loss_SDRO_avg_2 = torch.mean(Loss_SDRO_2)
250
+ Loss_SDRO_avg = Loss_SDRO_avg_1 - 0.5 * Loss_SDRO_avg_2
251
+
252
+ Loss_SDRO_avg = 1/probabilities[K_sample]* Loss_SDRO_avg
253
+ Loss_SDRO_avg.backward()
254
+ optimizer_theta.step()
255
+
256
+ # if torch.linalg.norm(self.model.linear.weight.data) > 0.95*np.sqrt(Lambda/2):
257
+ # self.model.linear.weight.data = self.model.linear.weight.data / torch.linalg.norm(self.model.linear.weight.data) * 0.95*np.sqrt(Lambda/2)
258
+
259
+
260
+
261
+
262
+ if __name__ == "__main__":
263
+ from sklearn.datasets import make_regression
264
+ from sklearn.linear_model import LinearRegression
265
+ import numpy as np
266
+
267
+ sample_size = 1000
268
+ feature_size = 10
269
+ X, y = make_regression(n_samples = sample_size, n_features = feature_size, noise = 1, random_state = 42)
270
+
271
+ method = Sinkhorn_DRO_Linear(0.1, 1000.0, maxiter=2000, input_dim=feature_size)
272
+ method.fit(X,y)
273
+ print(method.model.linear.weight)
274
+
275
+ model = LinearRegression(fit_intercept=True)
276
+ model.fit(X, y)
277
+ print(model.coef_)