synapse-sdk 2025.9.5__py3-none-any.whl → 2025.10.6__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.
Potentially problematic release.
This version of synapse-sdk might be problematic. Click here for more details.
- synapse_sdk/clients/base.py +129 -9
- synapse_sdk/devtools/docs/docs/api/clients/base.md +230 -8
- synapse_sdk/devtools/docs/docs/api/plugins/models.md +58 -3
- synapse_sdk/devtools/docs/docs/plugins/categories/neural-net-plugins/train-action-overview.md +663 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/pre-annotation-plugin-overview.md +198 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-action-development.md +1645 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-overview.md +717 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-template-development.md +1380 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-action.md +934 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-overview.md +585 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-template.md +715 -0
- synapse_sdk/devtools/docs/docs/plugins/export-plugins.md +39 -0
- synapse_sdk/devtools/docs/docs/plugins/plugins.md +12 -5
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/base.md +230 -8
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/plugins/models.md +114 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/neural-net-plugins/train-action-overview.md +621 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/pre-annotation-plugin-overview.md +198 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-action-development.md +1645 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-overview.md +717 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-template-development.md +1380 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-action.md +934 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-overview.md +585 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-template.md +715 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/export-plugins.md +39 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current.json +16 -4
- synapse_sdk/devtools/docs/sidebars.ts +45 -1
- synapse_sdk/plugins/README.md +487 -80
- synapse_sdk/plugins/categories/base.py +1 -0
- synapse_sdk/plugins/categories/export/actions/export/action.py +8 -3
- synapse_sdk/plugins/categories/export/actions/export/utils.py +108 -8
- synapse_sdk/plugins/categories/export/templates/config.yaml +18 -0
- synapse_sdk/plugins/categories/export/templates/plugin/export.py +97 -0
- synapse_sdk/plugins/categories/neural_net/actions/train.py +592 -22
- synapse_sdk/plugins/categories/neural_net/actions/tune.py +150 -3
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/action.py +10 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +145 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +97 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +250 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +284 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +87 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +127 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
- synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +2 -1
- synapse_sdk/plugins/categories/upload/actions/upload/action.py +8 -1
- synapse_sdk/plugins/categories/upload/actions/upload/context.py +0 -1
- synapse_sdk/plugins/categories/upload/actions/upload/models.py +134 -94
- synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +2 -2
- synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +6 -2
- synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +24 -9
- synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +130 -18
- synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +147 -37
- synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +10 -5
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +31 -6
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +65 -37
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +17 -2
- synapse_sdk/plugins/categories/upload/templates/README.md +394 -0
- synapse_sdk/plugins/models.py +62 -0
- synapse_sdk/utils/file/download.py +261 -0
- {synapse_sdk-2025.9.5.dist-info → synapse_sdk-2025.10.6.dist-info}/METADATA +15 -2
- {synapse_sdk-2025.9.5.dist-info → synapse_sdk-2025.10.6.dist-info}/RECORD +74 -43
- synapse_sdk/devtools/docs/docs/plugins/developing-upload-template.md +0 -1463
- synapse_sdk/devtools/docs/docs/plugins/upload-plugins.md +0 -1964
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/developing-upload-template.md +0 -1463
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/upload-plugins.md +0 -2077
- {synapse_sdk-2025.9.5.dist-info → synapse_sdk-2025.10.6.dist-info}/WHEEL +0 -0
- {synapse_sdk-2025.9.5.dist-info → synapse_sdk-2025.10.6.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-2025.9.5.dist-info → synapse_sdk-2025.10.6.dist-info}/licenses/LICENSE +0 -0
- {synapse_sdk-2025.9.5.dist-info → synapse_sdk-2025.10.6.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import tempfile
|
|
3
|
-
from decimal import Decimal
|
|
4
3
|
from pathlib import Path
|
|
5
|
-
from typing import Annotated
|
|
4
|
+
from typing import Annotated, Optional
|
|
6
5
|
|
|
7
|
-
from pydantic import AfterValidator, BaseModel, field_validator
|
|
6
|
+
from pydantic import AfterValidator, BaseModel, field_validator, model_validator
|
|
8
7
|
from pydantic_core import PydanticCustomError
|
|
9
8
|
|
|
10
9
|
from synapse_sdk.clients.exceptions import ClientError
|
|
@@ -13,57 +12,359 @@ from synapse_sdk.plugins.categories.decorators import register_action
|
|
|
13
12
|
from synapse_sdk.plugins.enums import PluginCategory, RunMethod
|
|
14
13
|
from synapse_sdk.plugins.models import Run
|
|
15
14
|
from synapse_sdk.utils.file import archive, get_temp_path, unarchive
|
|
15
|
+
from synapse_sdk.utils.module_loading import import_string
|
|
16
16
|
from synapse_sdk.utils.pydantic.validators import non_blank
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class TrainRun(Run):
|
|
20
|
+
is_tune = False
|
|
21
|
+
completed_samples = 0
|
|
22
|
+
num_samples = 0
|
|
23
|
+
checkpoint_output = None
|
|
24
|
+
|
|
20
25
|
def log_metric(self, category, key, value, **metrics):
|
|
21
26
|
# TODO validate input via plugin config
|
|
22
|
-
|
|
27
|
+
data = {'category': category, 'key': key, 'value': value, 'metrics': metrics}
|
|
28
|
+
|
|
29
|
+
# Automatically add trial_id when is_tune=True
|
|
30
|
+
if self.is_tune:
|
|
31
|
+
try:
|
|
32
|
+
from ray import train
|
|
33
|
+
|
|
34
|
+
context = train.get_context()
|
|
35
|
+
trial_id = context.get_trial_id()
|
|
36
|
+
if trial_id:
|
|
37
|
+
data['trial_id'] = trial_id
|
|
38
|
+
except Exception:
|
|
39
|
+
# If Ray context is not available, continue without trial_id
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
self.log('metric', data)
|
|
23
43
|
|
|
24
44
|
def log_visualization(self, category, group, index, image, **meta):
|
|
25
45
|
# TODO validate input via plugin config
|
|
26
|
-
|
|
46
|
+
data = {'category': category, 'group': group, 'index': index, **meta}
|
|
47
|
+
|
|
48
|
+
# Automatically add trial_id when is_tune=True
|
|
49
|
+
if self.is_tune:
|
|
50
|
+
try:
|
|
51
|
+
from ray import train
|
|
52
|
+
|
|
53
|
+
context = train.get_context()
|
|
54
|
+
trial_id = context.get_trial_id()
|
|
55
|
+
if trial_id:
|
|
56
|
+
data['trial_id'] = trial_id
|
|
57
|
+
except Exception:
|
|
58
|
+
# If Ray context is not available, continue without trial_id
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
self.log('visualization', data, file=image)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class SearchAlgo(BaseModel):
|
|
65
|
+
"""
|
|
66
|
+
Configuration for Ray Tune search algorithms.
|
|
67
|
+
|
|
68
|
+
Supported algorithms:
|
|
69
|
+
- 'bayesoptsearch': Bayesian optimization using Gaussian Processes
|
|
70
|
+
- 'hyperoptsearch': Tree-structured Parzen Estimator (TPE)
|
|
71
|
+
- 'basicvariantgenerator': Random search (default)
|
|
72
|
+
|
|
73
|
+
Attributes:
|
|
74
|
+
name (str): Name of the search algorithm (case-insensitive)
|
|
75
|
+
points_to_evaluate (Optional[dict]): Optional initial hyperparameter
|
|
76
|
+
configurations to evaluate before starting optimization
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
{
|
|
80
|
+
"name": "hyperoptsearch",
|
|
81
|
+
"points_to_evaluate": [
|
|
82
|
+
{"learning_rate": 0.001, "batch_size": 32}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
name: str
|
|
88
|
+
points_to_evaluate: Optional[dict] = None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Scheduler(BaseModel):
|
|
92
|
+
"""
|
|
93
|
+
Configuration for Ray Tune schedulers.
|
|
94
|
+
|
|
95
|
+
Supported schedulers:
|
|
96
|
+
- 'fifo': First-In-First-Out scheduler (default, runs all trials)
|
|
97
|
+
- 'hyperband': HyperBand early stopping scheduler
|
|
27
98
|
|
|
99
|
+
Attributes:
|
|
100
|
+
name (str): Name of the scheduler (case-insensitive)
|
|
101
|
+
options (Optional[str]): Optional scheduler-specific configuration parameters
|
|
28
102
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
103
|
+
Example:
|
|
104
|
+
{
|
|
105
|
+
"name": "hyperband",
|
|
106
|
+
"options": {
|
|
107
|
+
"max_t": 100,
|
|
108
|
+
"reduction_factor": 3
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
name: str
|
|
114
|
+
options: Optional[str] = None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class TuneConfig(BaseModel):
|
|
118
|
+
"""
|
|
119
|
+
Configuration for Ray Tune hyperparameter optimization.
|
|
120
|
+
|
|
121
|
+
Used when is_tune=True to configure the hyperparameter search process.
|
|
122
|
+
|
|
123
|
+
Attributes:
|
|
124
|
+
mode (Optional[str]): Optimization mode - 'max' or 'min'
|
|
125
|
+
metric (Optional[str]): Name of the metric to optimize
|
|
126
|
+
num_samples (int): Number of hyperparameter configurations to try (default: 1)
|
|
127
|
+
max_concurrent_trials (Optional[int]): Maximum number of trials to run in parallel
|
|
128
|
+
search_alg (Optional[SearchAlgo]): Search algorithm configuration
|
|
129
|
+
scheduler (Optional[Scheduler]): Trial scheduler configuration
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
{
|
|
133
|
+
"mode": "max",
|
|
134
|
+
"metric": "accuracy",
|
|
135
|
+
"num_samples": 20,
|
|
136
|
+
"max_concurrent_trials": 4,
|
|
137
|
+
"search_alg": {
|
|
138
|
+
"name": "hyperoptsearch"
|
|
139
|
+
},
|
|
140
|
+
"scheduler": {
|
|
141
|
+
"name": "hyperband",
|
|
142
|
+
"options": {"max_t": 100}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
mode: Optional[str] = None
|
|
148
|
+
metric: Optional[str] = None
|
|
149
|
+
num_samples: int = 1
|
|
150
|
+
max_concurrent_trials: Optional[int] = None
|
|
151
|
+
search_alg: Optional[SearchAlgo] = None
|
|
152
|
+
scheduler: Optional[Scheduler] = None
|
|
33
153
|
|
|
34
154
|
|
|
35
155
|
class TrainParams(BaseModel):
|
|
156
|
+
"""
|
|
157
|
+
Parameters for TrainAction supporting both regular training and hyperparameter tuning.
|
|
158
|
+
|
|
159
|
+
Attributes:
|
|
160
|
+
name (str): Name for the training/tuning job
|
|
161
|
+
description (str): Description of the job
|
|
162
|
+
checkpoint (int | None): Optional checkpoint ID to resume from
|
|
163
|
+
dataset (int): Dataset ID to use for training
|
|
164
|
+
is_tune (bool): Enable hyperparameter tuning mode (default: False)
|
|
165
|
+
tune_config (Optional[TuneConfig]): Tune configuration (required when is_tune=True)
|
|
166
|
+
num_cpus (Optional[int]): CPUs per trial (tuning mode only)
|
|
167
|
+
num_gpus (Optional[int]): GPUs per trial (tuning mode only)
|
|
168
|
+
hyperparameter (Optional[Any]): Fixed hyperparameters (required when is_tune=False)
|
|
169
|
+
hyperparameters (Optional[list]): Hyperparameter search space (required when is_tune=True)
|
|
170
|
+
|
|
171
|
+
Hyperparameter format when is_tune=True:
|
|
172
|
+
Each item in hyperparameters list must have:
|
|
173
|
+
- 'name': Parameter name (string)
|
|
174
|
+
- 'type': Distribution type (string)
|
|
175
|
+
- Type-specific parameters:
|
|
176
|
+
- uniform/quniform: 'min', 'max'
|
|
177
|
+
- loguniform/qloguniform: 'min', 'max', 'base'
|
|
178
|
+
- randn/qrandn: 'mean', 'sd'
|
|
179
|
+
- randint/qrandint: 'min', 'max'
|
|
180
|
+
- lograndint/qlograndint: 'min', 'max', 'base'
|
|
181
|
+
- choice/grid_search: 'options'
|
|
182
|
+
|
|
183
|
+
Example (Training mode):
|
|
184
|
+
{
|
|
185
|
+
"name": "my_training",
|
|
186
|
+
"dataset": 123,
|
|
187
|
+
"is_tune": false,
|
|
188
|
+
"hyperparameter": {
|
|
189
|
+
"epochs": 100,
|
|
190
|
+
"batch_size": 32,
|
|
191
|
+
"learning_rate": 0.001
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
Example (Tuning mode):
|
|
196
|
+
{
|
|
197
|
+
"name": "my_tuning",
|
|
198
|
+
"dataset": 123,
|
|
199
|
+
"is_tune": true,
|
|
200
|
+
"hyperparameters": [
|
|
201
|
+
{"name": "batch_size", "type": "choice", "options": [16, 32, 64]},
|
|
202
|
+
{"name": "learning_rate", "type": "loguniform", "min": 0.0001, "max": 0.01, "base": 10},
|
|
203
|
+
{"name": "epochs", "type": "randint", "min": 5, "max": 15}
|
|
204
|
+
],
|
|
205
|
+
"tune_config": {
|
|
206
|
+
"mode": "max",
|
|
207
|
+
"metric": "accuracy",
|
|
208
|
+
"num_samples": 10
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
"""
|
|
212
|
+
|
|
36
213
|
name: Annotated[str, AfterValidator(non_blank)]
|
|
37
214
|
description: str
|
|
38
215
|
checkpoint: int | None
|
|
39
216
|
dataset: int
|
|
40
|
-
|
|
217
|
+
is_tune: bool = False
|
|
218
|
+
tune_config: Optional[TuneConfig] = None
|
|
219
|
+
num_cpus: Optional[int] = None
|
|
220
|
+
num_gpus: Optional[int] = None
|
|
221
|
+
hyperparameter: Optional[dict] = None # plan to be deprecated
|
|
222
|
+
hyperparameters: Optional[list] = None
|
|
223
|
+
|
|
224
|
+
@field_validator('hyperparameter', mode='before')
|
|
225
|
+
@classmethod
|
|
226
|
+
def validate_hyperparameter(cls, v, info):
|
|
227
|
+
"""Validate hyperparameter for train mode (is_tune=False)"""
|
|
228
|
+
# Get is_tune flag to determine if this field should be validated
|
|
229
|
+
is_tune = info.data.get('is_tune', False)
|
|
230
|
+
|
|
231
|
+
# If is_tune=True, hyperparameter should be None/not used
|
|
232
|
+
# Just return whatever was passed (will be validated in model_validator)
|
|
233
|
+
if is_tune:
|
|
234
|
+
return v
|
|
235
|
+
|
|
236
|
+
# For train mode, hyperparameter should be a dict
|
|
237
|
+
if isinstance(v, dict):
|
|
238
|
+
return v
|
|
239
|
+
elif isinstance(v, list):
|
|
240
|
+
raise ValueError(
|
|
241
|
+
'hyperparameter must be a dict, not a list. '
|
|
242
|
+
'If you want to use hyperparameter tuning, '
|
|
243
|
+
'set "is_tune": true and use "hyperparameters" instead.'
|
|
244
|
+
)
|
|
245
|
+
else:
|
|
246
|
+
raise ValueError('hyperparameter must be a dict')
|
|
247
|
+
|
|
248
|
+
@field_validator('hyperparameters', mode='before')
|
|
249
|
+
@classmethod
|
|
250
|
+
def validate_hyperparameters(cls, v, info):
|
|
251
|
+
"""Validate hyperparameters for tune mode (is_tune=True)"""
|
|
252
|
+
# Get is_tune flag to determine if this field should be validated
|
|
253
|
+
is_tune = info.data.get('is_tune', False)
|
|
254
|
+
|
|
255
|
+
# If is_tune=False, hyperparameters should be None/not used
|
|
256
|
+
# Just return whatever was passed (will be validated in model_validator)
|
|
257
|
+
if not is_tune:
|
|
258
|
+
return v
|
|
259
|
+
|
|
260
|
+
# For tune mode, hyperparameters should be a list
|
|
261
|
+
if isinstance(v, list):
|
|
262
|
+
return v
|
|
263
|
+
elif isinstance(v, dict):
|
|
264
|
+
raise ValueError(
|
|
265
|
+
'hyperparameters must be a list, not a dict. '
|
|
266
|
+
'If you want to use fixed hyperparameters for training, '
|
|
267
|
+
'set "is_tune": false and use "hyperparameter" instead.'
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
raise ValueError('hyperparameters must be a list')
|
|
41
271
|
|
|
42
272
|
@field_validator('name')
|
|
43
273
|
@staticmethod
|
|
44
274
|
def unique_name(value, info):
|
|
45
275
|
action = info.context['action']
|
|
46
276
|
client = action.client
|
|
277
|
+
is_tune = info.data.get('is_tune', False)
|
|
47
278
|
try:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
279
|
+
if not is_tune:
|
|
280
|
+
model_exists = client.exists('list_models', params={'name': value})
|
|
281
|
+
job_exists = client.exists(
|
|
282
|
+
'list_jobs',
|
|
283
|
+
params={
|
|
284
|
+
'ids_ex': action.job_id,
|
|
285
|
+
'category': 'neural_net',
|
|
286
|
+
'job__action': 'train',
|
|
287
|
+
'is_active': True,
|
|
288
|
+
'params': f'name:{value.replace(":", "%3A")}',
|
|
289
|
+
},
|
|
290
|
+
)
|
|
291
|
+
assert not model_exists and not job_exists, '존재하는 학습 이름입니다.'
|
|
292
|
+
else:
|
|
293
|
+
job_exists = client.exists(
|
|
294
|
+
'list_jobs',
|
|
295
|
+
params={
|
|
296
|
+
'ids_ex': action.job_id,
|
|
297
|
+
'category': 'neural_net',
|
|
298
|
+
'job__action': 'train',
|
|
299
|
+
'is_active': True,
|
|
300
|
+
'params': f'name:{value}',
|
|
301
|
+
},
|
|
302
|
+
)
|
|
303
|
+
assert not job_exists, '존재하는 튜닝 작업 이름입니다.'
|
|
60
304
|
except ClientError:
|
|
61
305
|
raise PydanticCustomError('client_error', '')
|
|
62
306
|
return value
|
|
63
307
|
|
|
308
|
+
@model_validator(mode='after')
|
|
309
|
+
def validate_tune_params(self):
|
|
310
|
+
if self.is_tune:
|
|
311
|
+
# When is_tune=True, hyperparameters is required
|
|
312
|
+
if self.hyperparameters is None:
|
|
313
|
+
raise ValueError('hyperparameters is required when is_tune=True')
|
|
314
|
+
if self.hyperparameter is not None:
|
|
315
|
+
raise ValueError('hyperparameter should not be provided when is_tune=True, use hyperparameters instead')
|
|
316
|
+
if self.tune_config is None:
|
|
317
|
+
raise ValueError('tune_config is required when is_tune=True')
|
|
318
|
+
else:
|
|
319
|
+
# When is_tune=False, hyperparameter is required
|
|
320
|
+
if self.hyperparameter is None:
|
|
321
|
+
raise ValueError('hyperparameter is required when is_tune=False')
|
|
322
|
+
if self.hyperparameters is not None:
|
|
323
|
+
raise ValueError(
|
|
324
|
+
'hyperparameters should not be provided when is_tune=False, use hyperparameter instead'
|
|
325
|
+
)
|
|
326
|
+
return self
|
|
327
|
+
|
|
64
328
|
|
|
65
329
|
@register_action
|
|
66
330
|
class TrainAction(Action):
|
|
331
|
+
"""
|
|
332
|
+
**Important notes when using train with is_tune=True:**
|
|
333
|
+
|
|
334
|
+
1. Path to the model output (which is the return value of your train function)
|
|
335
|
+
should be set to the checkpoint_output attribute of the run object **before**
|
|
336
|
+
starting the training.
|
|
337
|
+
2. Before exiting the training function, report the results to Tune.
|
|
338
|
+
3. When using own tune.py, take note of the difference in the order of parameters.
|
|
339
|
+
tune() function starts with hyperparameter, run, dataset, checkpoint, **kwargs
|
|
340
|
+
whereas the train() function starts with run, dataset, hyperparameter, checkpoint, **kwargs.
|
|
341
|
+
----
|
|
342
|
+
1)
|
|
343
|
+
Set the output path for the checkpoint to export best model
|
|
344
|
+
|
|
345
|
+
output_path = Path('path/to/your/weights')
|
|
346
|
+
run.checkpoint_output = str(output_path)
|
|
347
|
+
|
|
348
|
+
2)
|
|
349
|
+
Before exiting the training function, report the results to Tune.
|
|
350
|
+
The results_dict should contain the metrics you want to report.
|
|
351
|
+
|
|
352
|
+
Example: (In train function)
|
|
353
|
+
results_dict = {
|
|
354
|
+
"accuracy": accuracy,
|
|
355
|
+
"loss": loss,
|
|
356
|
+
# Add other metrics as needed
|
|
357
|
+
}
|
|
358
|
+
if hasattr(self.dm_run, 'is_tune') and self.dm_run.is_tune:
|
|
359
|
+
tune.report(results_dict, checkpoint=tune.Checkpoint.from_directory(self.dm_run.checkpoint_output))
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
3)
|
|
363
|
+
tune() function takes hyperparameter, run, dataset, checkpoint, **kwargs in that order
|
|
364
|
+
whereas train() function takes run, dataset, hyperparameter, checkpoint, **kwargs in that order.
|
|
365
|
+
|
|
366
|
+
"""
|
|
367
|
+
|
|
67
368
|
name = 'train'
|
|
68
369
|
category = PluginCategory.NEURAL_NET
|
|
69
370
|
method = RunMethod.JOB
|
|
@@ -76,12 +377,22 @@ class TrainAction(Action):
|
|
|
76
377
|
'train': {
|
|
77
378
|
'proportion': 75,
|
|
78
379
|
},
|
|
380
|
+
'trials': {
|
|
381
|
+
'proportion': 90,
|
|
382
|
+
},
|
|
79
383
|
'model_upload': {
|
|
80
384
|
'proportion': 5,
|
|
81
385
|
},
|
|
82
386
|
}
|
|
83
387
|
|
|
84
388
|
def start(self):
|
|
389
|
+
if self.params.get('is_tune', False):
|
|
390
|
+
return self._start_tune()
|
|
391
|
+
else:
|
|
392
|
+
return self._start_train()
|
|
393
|
+
|
|
394
|
+
def _start_train(self):
|
|
395
|
+
"""Original train logic"""
|
|
85
396
|
hyperparameter = self.params['hyperparameter']
|
|
86
397
|
|
|
87
398
|
# download dataset
|
|
@@ -107,6 +418,87 @@ class TrainAction(Action):
|
|
|
107
418
|
self.run.end_log()
|
|
108
419
|
return {'model_id': model['id'] if model else None}
|
|
109
420
|
|
|
421
|
+
def _start_tune(self):
|
|
422
|
+
"""Tune logic using Ray Tune for hyperparameter optimization"""
|
|
423
|
+
from ray import tune
|
|
424
|
+
|
|
425
|
+
# Mark run as tune
|
|
426
|
+
self.run.is_tune = True
|
|
427
|
+
|
|
428
|
+
# download dataset
|
|
429
|
+
self.run.log_message('Preparing dataset for hyperparameter tuning.')
|
|
430
|
+
input_dataset = self.get_dataset()
|
|
431
|
+
|
|
432
|
+
# retrieve checkpoint
|
|
433
|
+
checkpoint = None
|
|
434
|
+
if self.params['checkpoint']:
|
|
435
|
+
self.run.log_message('Retrieving checkpoint.')
|
|
436
|
+
checkpoint = self.get_model(self.params['checkpoint'])
|
|
437
|
+
|
|
438
|
+
# train dataset
|
|
439
|
+
self.run.log_message('Starting training for hyperparameter tuning.')
|
|
440
|
+
|
|
441
|
+
# Save num_samples to TrainRun for logging
|
|
442
|
+
self.run.num_samples = self.params['tune_config']['num_samples']
|
|
443
|
+
|
|
444
|
+
entrypoint = self.entrypoint
|
|
445
|
+
if not self._tune_override_exists():
|
|
446
|
+
# entrypoint must be train entrypoint
|
|
447
|
+
def _tune(param_space, run, dataset, checkpoint=None, **kwargs):
|
|
448
|
+
return entrypoint(run, dataset, param_space, checkpoint, **kwargs)
|
|
449
|
+
|
|
450
|
+
entrypoint = _tune
|
|
451
|
+
|
|
452
|
+
trainable = tune.with_parameters(entrypoint, run=self.run, dataset=input_dataset, checkpoint=checkpoint)
|
|
453
|
+
|
|
454
|
+
tune_config = self.params['tune_config']
|
|
455
|
+
|
|
456
|
+
# Extract search_alg and scheduler as separate objects to avoid JSON serialization issues
|
|
457
|
+
search_alg = self.convert_tune_search_alg(tune_config)
|
|
458
|
+
scheduler = self.convert_tune_scheduler(tune_config)
|
|
459
|
+
|
|
460
|
+
# Create a copy of tune_config without non-serializable objects
|
|
461
|
+
tune_config_dict = {
|
|
462
|
+
'mode': tune_config.get('mode'),
|
|
463
|
+
'metric': tune_config.get('metric'),
|
|
464
|
+
'num_samples': tune_config.get('num_samples', 1),
|
|
465
|
+
'max_concurrent_trials': tune_config.get('max_concurrent_trials'),
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
# Add search_alg and scheduler to tune_config_dict only if they exist
|
|
469
|
+
if search_alg is not None:
|
|
470
|
+
tune_config_dict['search_alg'] = search_alg
|
|
471
|
+
if scheduler is not None:
|
|
472
|
+
tune_config_dict['scheduler'] = scheduler
|
|
473
|
+
|
|
474
|
+
hyperparameters = self.params['hyperparameters']
|
|
475
|
+
param_space = self.convert_tune_params(hyperparameters)
|
|
476
|
+
temp_path = tempfile.TemporaryDirectory()
|
|
477
|
+
|
|
478
|
+
tuner = tune.Tuner(
|
|
479
|
+
tune.with_resources(trainable, resources=self.tune_resources),
|
|
480
|
+
tune_config=tune.TuneConfig(**tune_config_dict),
|
|
481
|
+
run_config=tune.RunConfig(
|
|
482
|
+
name=f'synapse_tune_hpo_{self.job_id}',
|
|
483
|
+
log_to_file=('stdout.log', 'stderr.log'),
|
|
484
|
+
storage_path=temp_path.name,
|
|
485
|
+
),
|
|
486
|
+
param_space=param_space,
|
|
487
|
+
)
|
|
488
|
+
result = tuner.fit()
|
|
489
|
+
|
|
490
|
+
best_result = result.get_best_result()
|
|
491
|
+
|
|
492
|
+
# upload model_data
|
|
493
|
+
self.run.log_message('Registering best model data.')
|
|
494
|
+
self.run.set_progress(0, 1, category='model_upload')
|
|
495
|
+
self.create_model_from_result(best_result)
|
|
496
|
+
self.run.set_progress(1, 1, category='model_upload')
|
|
497
|
+
|
|
498
|
+
self.run.end_log()
|
|
499
|
+
|
|
500
|
+
return {'best_result': best_result.config}
|
|
501
|
+
|
|
110
502
|
def get_dataset(self):
|
|
111
503
|
client = self.run.client
|
|
112
504
|
assert bool(client)
|
|
@@ -157,3 +549,181 @@ class TrainAction(Action):
|
|
|
157
549
|
'configuration': configuration,
|
|
158
550
|
**params,
|
|
159
551
|
})
|
|
552
|
+
|
|
553
|
+
@property
|
|
554
|
+
def tune_resources(self):
|
|
555
|
+
resources = {}
|
|
556
|
+
for option in ['num_cpus', 'num_gpus']:
|
|
557
|
+
option_value = self.params.get(option)
|
|
558
|
+
if option_value:
|
|
559
|
+
# Remove the 'num_' prefix and trailing s from the option name
|
|
560
|
+
resources[(lambda s: s[4:-1])(option)] = option_value
|
|
561
|
+
return resources
|
|
562
|
+
|
|
563
|
+
def create_model_from_result(self, result):
|
|
564
|
+
params = copy.deepcopy(self.params)
|
|
565
|
+
configuration_fields = ['hyperparameters']
|
|
566
|
+
configuration = {field: params.pop(field) for field in configuration_fields}
|
|
567
|
+
|
|
568
|
+
with tempfile.TemporaryDirectory() as temp_path:
|
|
569
|
+
archive_path = Path(temp_path, 'archive.zip')
|
|
570
|
+
|
|
571
|
+
# Archive tune results
|
|
572
|
+
# https://docs.ray.io/en/latest/tune/tutorials/tune_get_data_in_and_out.html#getting-data-out-of-tune-using-checkpoints-other-artifacts
|
|
573
|
+
archive(result.path, archive_path)
|
|
574
|
+
|
|
575
|
+
return self.client.create_model({
|
|
576
|
+
'plugin': self.plugin_release.plugin,
|
|
577
|
+
'version': self.plugin_release.version,
|
|
578
|
+
'file': str(archive_path),
|
|
579
|
+
'configuration': configuration,
|
|
580
|
+
**params,
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
@staticmethod
|
|
584
|
+
def convert_tune_scheduler(tune_config):
|
|
585
|
+
"""
|
|
586
|
+
Convert YAML hyperparameter configuration to a Ray Tune scheduler.
|
|
587
|
+
|
|
588
|
+
Args:
|
|
589
|
+
tune_config (dict): Hyperparameter configuration.
|
|
590
|
+
|
|
591
|
+
Returns:
|
|
592
|
+
object: Ray Tune scheduler instance.
|
|
593
|
+
|
|
594
|
+
Supported schedulers:
|
|
595
|
+
- 'fifo': FIFOScheduler (default)
|
|
596
|
+
- 'hyperband': HyperBandScheduler
|
|
597
|
+
"""
|
|
598
|
+
|
|
599
|
+
from ray.tune.schedulers import (
|
|
600
|
+
ASHAScheduler,
|
|
601
|
+
FIFOScheduler,
|
|
602
|
+
HyperBandScheduler,
|
|
603
|
+
MedianStoppingRule,
|
|
604
|
+
PopulationBasedTraining,
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
if tune_config.get('scheduler') is None:
|
|
608
|
+
return None
|
|
609
|
+
|
|
610
|
+
scheduler_map = {
|
|
611
|
+
'fifo': FIFOScheduler,
|
|
612
|
+
'asha': ASHAScheduler,
|
|
613
|
+
'hyperband': HyperBandScheduler,
|
|
614
|
+
'pbt': PopulationBasedTraining,
|
|
615
|
+
'median': MedianStoppingRule,
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
scheduler_type = tune_config['scheduler'].get('name', 'fifo').lower()
|
|
619
|
+
scheduler_class = scheduler_map.get(scheduler_type, FIFOScheduler)
|
|
620
|
+
|
|
621
|
+
# 옵션이 있는 경우 전달하고, 없으면 기본 생성자 호출
|
|
622
|
+
options = tune_config['scheduler'].get('options')
|
|
623
|
+
|
|
624
|
+
# options가 None이거나 빈 딕셔너리가 아닌 경우에만 전달
|
|
625
|
+
scheduler = scheduler_class(**options) if options else scheduler_class()
|
|
626
|
+
|
|
627
|
+
return scheduler
|
|
628
|
+
|
|
629
|
+
@staticmethod
|
|
630
|
+
def convert_tune_search_alg(tune_config):
|
|
631
|
+
"""
|
|
632
|
+
Convert YAML hyperparameter configuration to Ray Tune search algorithm.
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
tune_config (dict): Hyperparameter configuration.
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
object: Ray Tune search algorithm instance or None
|
|
639
|
+
|
|
640
|
+
Supported search algorithms:
|
|
641
|
+
- 'bayesoptsearch': Bayesian optimization
|
|
642
|
+
- 'hyperoptsearch': Tree-structured Parzen Estimator
|
|
643
|
+
- 'basicvariantgenerator': Random search (default)
|
|
644
|
+
"""
|
|
645
|
+
|
|
646
|
+
if tune_config.get('search_alg') is None:
|
|
647
|
+
return None
|
|
648
|
+
|
|
649
|
+
search_alg_name = tune_config['search_alg']['name'].lower()
|
|
650
|
+
metric = tune_config['metric']
|
|
651
|
+
mode = tune_config['mode']
|
|
652
|
+
points_to_evaluate = tune_config['search_alg'].get('points_to_evaluate', None)
|
|
653
|
+
|
|
654
|
+
if search_alg_name == 'axsearch':
|
|
655
|
+
from ray.tune.search.ax import AxSearch
|
|
656
|
+
|
|
657
|
+
search_alg = AxSearch(metric=metric, mode=mode)
|
|
658
|
+
elif search_alg_name == 'bayesoptsearch':
|
|
659
|
+
from ray.tune.search.bayesopt import BayesOptSearch
|
|
660
|
+
|
|
661
|
+
search_alg = BayesOptSearch(metric=metric, mode=mode)
|
|
662
|
+
elif search_alg_name == 'hyperoptsearch':
|
|
663
|
+
from ray.tune.search.hyperopt import HyperOptSearch
|
|
664
|
+
|
|
665
|
+
search_alg = HyperOptSearch(metric=metric, mode=mode)
|
|
666
|
+
elif search_alg_name == 'optunasearch':
|
|
667
|
+
from ray.tune.search.optuna import OptunaSearch
|
|
668
|
+
|
|
669
|
+
search_alg = OptunaSearch(metric=metric, mode=mode)
|
|
670
|
+
elif search_alg_name == 'basicvariantgenerator':
|
|
671
|
+
from ray.tune.search.basic_variant import BasicVariantGenerator
|
|
672
|
+
|
|
673
|
+
search_alg = BasicVariantGenerator(points_to_evaluate=points_to_evaluate)
|
|
674
|
+
else:
|
|
675
|
+
raise ValueError(
|
|
676
|
+
f'Unsupported search algorithm: {search_alg_name}. '
|
|
677
|
+
f'Supported algorithms are: bayesoptsearch, hyperoptsearch, basicvariantgenerator'
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
return search_alg
|
|
681
|
+
|
|
682
|
+
@staticmethod
|
|
683
|
+
def convert_tune_params(param_list):
|
|
684
|
+
"""
|
|
685
|
+
Convert YAML hyperparameter configuration to Ray Tune parameter dictionary.
|
|
686
|
+
|
|
687
|
+
Args:
|
|
688
|
+
param_list (list): List of hyperparameter configurations.
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
dict: Ray Tune parameter dictionary
|
|
692
|
+
"""
|
|
693
|
+
from ray import tune
|
|
694
|
+
|
|
695
|
+
param_handlers = {
|
|
696
|
+
'uniform': lambda p: tune.uniform(p['min'], p['max']),
|
|
697
|
+
'quniform': lambda p: tune.quniform(p['min'], p['max']),
|
|
698
|
+
'loguniform': lambda p: tune.loguniform(p['min'], p['max'], p['base']),
|
|
699
|
+
'qloguniform': lambda p: tune.qloguniform(p['min'], p['max'], p['base']),
|
|
700
|
+
'randn': lambda p: tune.randn(p['mean'], p['sd']),
|
|
701
|
+
'qrandn': lambda p: tune.qrandn(p['mean'], p['sd']),
|
|
702
|
+
'randint': lambda p: tune.randint(p['min'], p['max']),
|
|
703
|
+
'qrandint': lambda p: tune.qrandint(p['min'], p['max']),
|
|
704
|
+
'lograndint': lambda p: tune.lograndint(p['min'], p['max'], p['base']),
|
|
705
|
+
'qlograndint': lambda p: tune.qlograndint(p['min'], p['max'], p['base']),
|
|
706
|
+
'choice': lambda p: tune.choice(p['options']),
|
|
707
|
+
'grid_search': lambda p: tune.grid_search(p['options']),
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
param_space = {}
|
|
711
|
+
|
|
712
|
+
for param in param_list:
|
|
713
|
+
name = param['name']
|
|
714
|
+
param_type = param['type']
|
|
715
|
+
|
|
716
|
+
if param_type in param_handlers:
|
|
717
|
+
param_space[name] = param_handlers[param_type](param)
|
|
718
|
+
else:
|
|
719
|
+
raise ValueError(f'Unknown parameter type: {param_type}')
|
|
720
|
+
|
|
721
|
+
return param_space
|
|
722
|
+
|
|
723
|
+
@staticmethod
|
|
724
|
+
def _tune_override_exists(module_path='plugin.tune') -> bool:
|
|
725
|
+
try:
|
|
726
|
+
import_string(module_path)
|
|
727
|
+
return True
|
|
728
|
+
except ImportError:
|
|
729
|
+
return False
|