junshan-kit 2.5.1__py2.py3-none-any.whl → 2.8.5__py2.py3-none-any.whl

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.
@@ -0,0 +1,290 @@
1
+ """
2
+ ----------------------------------------------------------------------
3
+ >>> Author : Junshan Yin
4
+ >>> Last Updated : 2025-12-19
5
+ ----------------------------------------------------------------------
6
+ """
7
+ import math, os
8
+ import matplotlib.pyplot as plt
9
+ import numpy as np
10
+ import matplotlib as mpl
11
+ from collections import defaultdict
12
+ from junshan_kit import kit, ParametersHub
13
+
14
+ def marker_schedule(marker_schedule=None):
15
+
16
+ if marker_schedule == "SPBM":
17
+ based_marker = {
18
+ "ADAM": "s", # square
19
+ "ALR-SMAG": "h", # pixel marker
20
+ "Bundle": "o", # circle
21
+ "SGD": "p", # pentagon
22
+ "SPSmax": "4", # tri-right
23
+ "SPBM-PF": "*", # star
24
+ "SPBM-TR": "s", # star
25
+ "SPBM-PF-NoneCut": "s", # circle
26
+ "SPBM-TR-NoneCut": "s", # circle
27
+ }
28
+
29
+ else:
30
+ based_marker = {
31
+ "point": ".", # point marker
32
+ "pixel": ",", # pixel marker
33
+ "circle": "o", # circle
34
+ "triangle_down": "v", # down triangle
35
+ "triangle_up": "^", # up triangle
36
+ "triangle_left": "<", # left triangle
37
+ "triangle_right": ">", # right triangle
38
+ "tri_down": "1", # tri-down
39
+ "tri_up": "2", # tri-up
40
+ "tri_left": "3", # tri-left
41
+ "tri_right": "4", # tri-right
42
+ "square": "s", # square
43
+ "pentagon": "p", # pentagon
44
+ "star": "*", # star
45
+ "hexagon1": "h", # hexagon 1
46
+ "hexagon2": "H", # hexagon 2
47
+ "plus": "+", # plus
48
+ "x": "x", # x
49
+ "diamond": "D", # diamond
50
+ "thin_diamond": "d", # thin diamond
51
+ "vline": "|", # vertical line
52
+ "hline": "_", # horizontal line
53
+ }
54
+
55
+ return based_marker
56
+
57
+
58
+ def colors_schedule(colors_schedule=None):
59
+
60
+ if colors_schedule == "SPBM":
61
+ based_color = {
62
+ "ADAM": "#7f7f7f",
63
+ "ALR-SMAG": "#796378",
64
+ "Bundle": "#17becf",
65
+ "SGD": "#2ca02c",
66
+ "SPSmax": "#BA6262",
67
+ "SPBM-PF": "#1f77b4",
68
+ "SPBM-TR": "#d62728",
69
+ "SPBM-PF-NoneCut": "#8c564b",
70
+ "SPBM-TR-NoneCut": "#e377c2",
71
+ }
72
+
73
+ else:
74
+ based_color = {
75
+ "ADAM": "#1f77b4",
76
+ "ALR-SMAG": "#ff7f0e",
77
+ "Bundle": "#2ca02c",
78
+ "SGD": "#d62728",
79
+ "SPSmax": "#9467bd",
80
+ "SPBM-PF": "#8c564b",
81
+ "SPBM-TR": "#e377c2",
82
+ "dddd": "#7f7f7f",
83
+ "xxx": "#bcbd22",
84
+ "ED": "#17becf",
85
+ }
86
+ return based_color
87
+
88
+
89
+ def Search_Paras(Paras, args, model_name, data_name, optimizer_name, metric_key = "training_loss"):
90
+
91
+ param_dict = Paras["Results_dict"][model_name][data_name][optimizer_name]
92
+
93
+ num_polts = len(param_dict)
94
+ cols = 3
95
+ rows = math.ceil(num_polts / cols)
96
+
97
+ fig, axes = plt.subplots(rows, cols, figsize=(5 * cols, 4 * rows))
98
+ axes = axes.flatten()
99
+
100
+ for idx, (param_str, info) in enumerate(param_dict.items()):
101
+ ax = axes[idx]
102
+ metric_list = info.get(metric_key, [])
103
+ # duration = info.get('duration', 0)
104
+ ax.plot(metric_list)
105
+ # ax.set_title(f"time:{duration:.8f}s - seed: {Paras['seed']}, ID: {Paras['time_str']} \n params = {param_str}", fontsize=10)
106
+ ax.set_title(f'time = {info["train_time"]:.2f}, seed: {Paras["seed"]}, ID: {Paras["time_str"]} \n params = {param_str}', fontsize=10)
107
+ ax.set_xlabel("epochs")
108
+ ax.set_ylabel(ParametersHub.fig_ylabel(metric_key))
109
+ ax.grid(True)
110
+ if Paras.get('use_log_scale', False) and any(k in metric_key for k in ['loss', 'grad']):
111
+ ax.set_yscale("log")
112
+
113
+
114
+ # Delete the redundant subfigures
115
+ for i in range(len(param_dict), len(axes)):
116
+ fig.delaxes(axes[i])
117
+
118
+
119
+ plt.suptitle(f'{model_name} on {data_name} - {optimizer_name}, (training, test) = ({Paras['train_data_num']}/{Paras['train_data_all_num']}, {Paras['test_data_num']}/{Paras['test_data_all_num']}), {Paras["device"]}, batch_size: {Paras["batch_size"]}', fontsize=16)
120
+ plt.tight_layout(rect=(0, 0, 1, 0.9))
121
+
122
+ filename = f'{Paras["Results_folder"]}/{metric_key}_{ParametersHub.model_abbr(model_name)}_{data_name}_{optimizer_name}.pdf'
123
+ fig.savefig(filename)
124
+ print(f"✅ Saved: {filename}")
125
+ plt.close('all')
126
+
127
+
128
+ def Read_Results_from_pkl(info_dict, Exp_name, model_name):
129
+ draw_data = defaultdict(dict)
130
+ for data_name, info in info_dict.items():
131
+ for optimizer_name, info_opt in info["optimizer"].items():
132
+
133
+ pkl_path = f'{Exp_name}/seed_{info["seed"]}/{model_name}/{data_name}/{optimizer_name}/train_{info["train_test"][0]}_test_{info["train_test"][1]}/Batch_size_{info["batch_size"]}/epoch_{info["epochs"]}/{info_opt["ID"]}/Results_{ParametersHub.model_abbr(model_name)}_{data_name}_{optimizer_name}.pkl'
134
+
135
+ data_ = kit.read_pkl_data(pkl_path)
136
+
137
+ param_str = ParametersHub.opt_paras_str(info["optimizer"][optimizer_name])
138
+
139
+ # draw_data[data_name][optimizer_name] = data_[param_str][info["metric_key"]]
140
+ # draw_data[data_name][optimizer_name][param_str] = param_str
141
+ # Store both metric list and parameter string
142
+ draw_data[data_name][optimizer_name] = {
143
+ "metrics": data_[param_str][info["metric_key"]],
144
+ "param_str": param_str
145
+ }
146
+
147
+ return draw_data
148
+
149
+
150
+
151
+ def Mul_Plot(model_name, info_dict, Exp_name = "SPBM", cols = 3, save_path = None, save_name = None, fig_show = False):
152
+ # matplotlib settings
153
+ mpl.rcParams['font.family'] = 'Times New Roman'
154
+ mpl.rcParams["mathtext.fontset"] = "stix"
155
+ mpl.rcParams["axes.unicode_minus"] = False
156
+ mpl.rcParams["font.size"] = 12
157
+ mpl.rcParams["font.family"] = "serif"
158
+
159
+ # Read data
160
+ draw_data = defaultdict(dict)
161
+ for data_name, info in info_dict.items():
162
+ for optimizer_name, info_opt in info["optimizer"].items():
163
+
164
+ pkl_path = f'{Exp_name}/seed_{info["seed"]}/{model_name}/{data_name}/{optimizer_name}/train_{info["train_test"][0]}_test_{info["train_test"][1]}/Batch_size_{info["batch_size"]}/epoch_{info["epochs"]}/{info_opt["ID"]}/Results_{ParametersHub.model_abbr(model_name)}_{data_name}_{optimizer_name}.pkl'
165
+
166
+ data_ = kit.read_pkl_data(pkl_path)
167
+
168
+ param_str = ParametersHub.opt_paras_str(info["optimizer"][optimizer_name])
169
+
170
+ draw_data[data_name][optimizer_name] = data_[param_str][info["metric_key"]]
171
+
172
+
173
+ # Draw figures
174
+ num_datasets = len(draw_data)
175
+
176
+ nrows = math.ceil(num_datasets / cols)
177
+
178
+ fig, axes = plt.subplots(nrows, cols, figsize=(5 * cols, 4 * nrows), squeeze=False)
179
+ axes = axes.flatten()
180
+
181
+ for idx, (data_name, info) in enumerate(draw_data.items()):
182
+ ax = axes[idx]
183
+ for optimizer_name, metric_list in info.items():
184
+ ax.plot(metric_list, label=optimizer_name, color = colors_schedule("SPBM")[optimizer_name])
185
+
186
+ # marker
187
+ if info_dict[data_name]["marker"] is not None:
188
+ x = np.array(info_dict[data_name]["marker"])
189
+
190
+ metric_list_arr = np.array(metric_list)
191
+
192
+ ax.scatter(x, metric_list_arr[x], marker=marker_schedule("SPBM")[optimizer_name], color = colors_schedule("SPBM")[optimizer_name])
193
+
194
+ ax.set_title(f'{data_name}', fontsize=12)
195
+ ax.set_xlabel("epochs", fontsize=12)
196
+ ax.set_ylabel(ParametersHub.fig_ylabel(info_dict[data_name]["metric_key"]), fontsize=12)
197
+ if any(k in info_dict[data_name]["metric_key"] for k in ['loss', 'grad']):
198
+ ax.set_yscale("log")
199
+ ax.grid(True)
200
+
201
+ # Hide redundant axes
202
+ for ax in axes[num_datasets:]:
203
+ ax.axis('off')
204
+
205
+ # legend
206
+ all_handles, all_labels = [], []
207
+ for ax in axes[:num_datasets]:
208
+ h, l = ax.get_legend_handles_labels()
209
+ all_handles.extend(h)
210
+ all_labels.extend(l)
211
+
212
+ # duplicate removal
213
+ unique = dict(zip(all_labels, all_handles))
214
+ handles = list(unique.values())
215
+ labels = list(unique.keys())
216
+
217
+ fig.legend(
218
+ handles,
219
+ labels,
220
+ loc="lower center",
221
+ bbox_to_anchor=(0.5, -0.08),
222
+ ncol=len(handles),
223
+ fontsize=12
224
+ )
225
+
226
+ plt.tight_layout()
227
+ if save_path is None:
228
+ save_path_ = f'{model_name}.pdf'
229
+ else:
230
+ os.makedirs(save_path, exist_ok=True)
231
+ save_path_ = f'{save_path}/{save_name}.pdf'
232
+ plt.savefig(save_path_, bbox_inches="tight")
233
+ if fig_show:
234
+ plt.show()
235
+ plt.close() # Colse the fig
236
+
237
+
238
+
239
+ def Opt_Paras_Plot(model_name, info_dict, Exp_name = "SPBM", save_path = None, save_name = None, fig_show = False):
240
+
241
+ mpl.rcParams['font.family'] = 'Times New Roman'
242
+ mpl.rcParams["mathtext.fontset"] = "stix"
243
+ mpl.rcParams["axes.unicode_minus"] = False
244
+ mpl.rcParams["font.size"] = 12
245
+ mpl.rcParams["font.family"] = "serif"
246
+
247
+ # Read data
248
+ draw_data = Read_Results_from_pkl(info_dict, Exp_name, model_name)
249
+
250
+ if len(draw_data) >1:
251
+ print('*' * 40)
252
+ print("Only one data can be drawn at a time.")
253
+ print(info_dict.keys())
254
+ print('*' * 40)
255
+ assert False
256
+
257
+ plt.figure(figsize=(9, 6)) # Optional: set figure size
258
+
259
+ data_name = None
260
+
261
+ for data_name, _info in draw_data.items():
262
+ for optimizer_name, metric_dict in _info.items():
263
+ plt.plot(metric_dict["metrics"], label=f'{optimizer_name}_{metric_dict["param_str"]}',
264
+ color=colors_schedule("SPBM")[optimizer_name])
265
+
266
+ if data_name is not None:
267
+ plt.title(f'{data_name}')
268
+
269
+ plt.legend(loc='upper center', bbox_to_anchor=(0.5, -0.15), ncol=1)
270
+ plt.grid(True)
271
+
272
+ if any(k in info_dict[data_name]["metric_key"] for k in ['loss', 'grad']):
273
+ plt.yscale("log")
274
+
275
+ plt.tight_layout() # Adjust layout so the legend fits
276
+ plt.xlabel("epochs") # Or whatever your x-axis represents
277
+ plt.ylabel(f'{ParametersHub.fig_ylabel(info_dict[data_name]["metric_key"])}')
278
+ if save_path is None:
279
+ save_path_ = f'{model_name}.pdf'
280
+ else:
281
+ os.makedirs(save_path, exist_ok=True)
282
+ save_path_ = f'{save_path}/{save_name}.pdf'
283
+ plt.savefig(save_path_, bbox_inches="tight")
284
+ if fig_show:
285
+ plt.show()
286
+
287
+ plt.close()
288
+
289
+
290
+
junshan_kit/ModelsHub.py CHANGED
@@ -5,7 +5,7 @@ import torch.nn as nn
5
5
 
6
6
 
7
7
  # ---------------- Build ResNet18 - Caltech101 -----------------------
8
- def Build_ResNet18_CALTECH101_Resize_32():
8
+ def Build_ResNet18_Caltech101_Resize_32():
9
9
 
10
10
  """
11
11
  1. Modify the first convolutional layer for smaller input (e.g., 32x32 instead of 224x224)
@@ -72,7 +72,7 @@ def Build_ResNet34_MNIST():
72
72
  return model
73
73
 
74
74
  # ---------------- Build ResNet34 - Caltech101 -----------------------
75
- def Build_ResNet34_CALTECH101_Resize_32():
75
+ def Build_ResNet34_Caltech101_Resize_32():
76
76
 
77
77
  model = resnet34(weights=None)
78
78
  model.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)
@@ -104,7 +104,7 @@ def Build_LeastSquares_CIFAR100():
104
104
  nn.Linear(3 * 32 * 32, 100))
105
105
 
106
106
  # ---------------- LeastSquares - Caltech101 ------------------
107
- def Build_LeastSquares_CALTECH101_Resize_32():
107
+ def Build_LeastSquares_Caltech101_Resize_32():
108
108
  return nn.Sequential(
109
109
  nn.Flatten(),
110
110
  nn.Linear(3*32*32, 101)
@@ -204,9 +204,36 @@ def Build_LogRegressionBinaryL2_w8a():
204
204
 
205
205
  # ---------------------------------------------------------
206
206
  def Build_LogRegressionBinaryL2_Adult_Income_Prediction():
207
- pass
207
+ return nn.Sequential(
208
+ nn.Linear(108, 1))
208
209
 
209
210
 
210
211
  def Build_LogRegressionBinaryL2_Credit_Card_Fraud_Detection():
211
- pass
212
+ return nn.Sequential(
213
+ nn.Linear(30, 1))
212
214
 
215
+
216
+ def Build_LogRegressionBinaryL2_Diabetes_Health_Indicators():
217
+ return nn.Sequential(
218
+ nn.Linear(52, 1))
219
+
220
+
221
+ def Build_LogRegressionBinaryL2_Electric_Vehicle_Population():
222
+ return nn.Sequential(
223
+ nn.Linear(835, 1))
224
+
225
+ def Build_LogRegressionBinaryL2_Global_House_Purchase():
226
+ return nn.Sequential(
227
+ nn.Linear(81, 1))
228
+
229
+ def Build_LogRegressionBinaryL2_Health_Lifestyle():
230
+ return nn.Sequential(
231
+ nn.Linear(15, 1))
232
+
233
+ def Build_LogRegressionBinaryL2_Homesite_Quote_Conversion():
234
+ return nn.Sequential(
235
+ nn.Linear(655, 1))
236
+
237
+ def Build_LogRegressionBinaryL2_TN_Weather_2020_2025():
238
+ return nn.Sequential(
239
+ nn.Linear(121, 1))
@@ -0,0 +1,130 @@
1
+ from junshan_kit.OptimizerHup import SPBM, SPBM_func
2
+ import torch, time, os
3
+ from torch.optim.optimizer import Optimizer
4
+ from torch.nn.utils import parameters_to_vector, vector_to_parameters
5
+
6
+
7
+ class SPSmax(Optimizer):
8
+ def __init__(self, params, model, hyperparams, Paras):
9
+ defaults = dict()
10
+ super().__init__(params, defaults)
11
+ self.model = model
12
+ self.c = hyperparams['c']
13
+ self.gamma = hyperparams['gamma']
14
+ if 'f_star' not in Paras or Paras['f_star'] is None:
15
+ self.f_star = 0
16
+ else:
17
+ self.f_star = Paras['f_star']
18
+ self.step_size = []
19
+
20
+ def step(self, closure=None):
21
+ if closure is None:
22
+ raise RuntimeError("Closure required for SPSmax")
23
+
24
+ # Reset the gradient and perform forward computation
25
+ loss = closure()
26
+
27
+ with torch.no_grad():
28
+ xk = parameters_to_vector(self.model.parameters())
29
+ # print(torch.norm(xk))
30
+ g_k = parameters_to_vector([p.grad if p.grad is not None else torch.zeros_like(p) for p in self.model.parameters()])
31
+
32
+ # Step-size
33
+ step_size = (loss - self.f_star) / ((self.c * torch.norm(g_k, p=2) ** 2) + 1e-8)
34
+ step_size = min(step_size, self.gamma)
35
+ self.step_size.append(step_size)
36
+
37
+ # Update
38
+ xk = xk - step_size * g_k
39
+
40
+ # print(len(self.f_his))
41
+ vector_to_parameters(xk, self.model.parameters())
42
+
43
+
44
+ # emporarily return loss (tensor type)
45
+ return loss
46
+
47
+
48
+ class ALR_SMAG(Optimizer):
49
+ def __init__(self, params, model, hyperparams, Paras):
50
+ defaults = dict()
51
+ super().__init__(params, defaults)
52
+ self.model = model
53
+ self.c = hyperparams['c']
54
+ self.eta_max = hyperparams['eta_max']
55
+ self.beta = hyperparams['beta']
56
+ if 'f_star' not in Paras or Paras['f_star'] is None:
57
+ self.f_star = 0
58
+ else:
59
+ self.f_star = Paras['f_star']
60
+ self.step_size = []
61
+ self.d_k = torch.zeros_like(parameters_to_vector(self.model.parameters()))
62
+
63
+ def step(self, closure=None):
64
+ if closure is None:
65
+ raise RuntimeError("Closure required for SPSmax")
66
+
67
+ # Reset the gradient and perform forward computation
68
+ loss = closure()
69
+
70
+ with torch.no_grad():
71
+ xk = parameters_to_vector(self.model.parameters())
72
+ # print(torch.norm(xk))
73
+ g_k = parameters_to_vector([p.grad if p.grad is not None else torch.zeros_like(p) for p in self.model.parameters()])
74
+
75
+ self.d_k = self.beta * self.d_k + g_k
76
+ # Step-size
77
+ step_size = (loss - self.f_star) / ((self.c * torch.norm(self.d_k, p=2) ** 2) + 1e-8)
78
+ step_size = min(step_size, self.eta_max)
79
+ self.step_size.append(step_size)
80
+
81
+ # Update
82
+ xk = xk - step_size * g_k
83
+
84
+ # print(len(self.f_his))
85
+ vector_to_parameters(xk, self.model.parameters())
86
+
87
+
88
+ # emporarily return loss (tensor type)
89
+ return loss
90
+
91
+
92
+ class Bundle(Optimizer):
93
+ def __init__(self, params, model, hyperparams, Paras):
94
+ defaults = dict()
95
+ super().__init__(params, defaults)
96
+ self.model = model
97
+ self.cutting_num = hyperparams['cutting_number']
98
+ self.delta = hyperparams['delta']
99
+ self.Paras = Paras
100
+
101
+ self.x_his, self.g_his, self.f_his = [], [], []
102
+
103
+ def step(self, closure=None):
104
+ if closure is None:
105
+ raise RuntimeError("Closure required for CuttingPlaneOptimizer")
106
+
107
+ # Reset the gradient and perform forward computation
108
+ loss = closure()
109
+
110
+ with torch.no_grad():
111
+ xk = parameters_to_vector(self.model.parameters())
112
+ # print(torch.norm(xk))
113
+ g_k = parameters_to_vector([p.grad if p.grad is not None else torch.zeros_like(p) for p in self.model.parameters()])
114
+
115
+ # Add cutting plane
116
+ x_his, f_his, g_his = SPBM_func.add_cutting(self.x_his, self.f_his, self.g_his,xk.detach().clone(), g_k.detach().clone(), loss.detach().clone(), self.cutting_num)
117
+
118
+ # the coefficient of dual problem
119
+ Gk, rk, ek = SPBM_func.get_var(x_his, f_his, g_his, self.delta)
120
+
121
+ # SOVER (dual)
122
+ xk = SPBM_func.bundle(Gk, ek, xk, self.delta, self.Paras)
123
+
124
+ # print(len(self.f_his))
125
+ vector_to_parameters(xk, self.model.parameters())
126
+
127
+ # loss(tensor)
128
+ return loss
129
+
130
+