diff --git a/configs/image_classification/local.yml b/configs/image_classification/local.yml new file mode 100644 index 0000000000..5d04f88332 --- /dev/null +++ b/configs/image_classification/local.yml @@ -0,0 +1,27 @@ +task: image_classification +base_model: google/vit-base-patch16-224 +project_name: autotrain-image-classification-model +log: tensorboard +backend: local + +data: + path: data/ + train_split: train # this folder inside data/ will be used for training, it contains the images in subfolders. + valid_split: null + column_mapping: + image_column: image + target_column: labels + +params: + epochs: 2 + batch_size: 4 + lr: 2e-5 + optimizer: adamw_torch + scheduler: linear + gradient_accumulation: 1 + mixed_precision: fp16 + +hub: + username: ${HF_USERNAME} + token: ${HF_TOKEN} + push_to_hub: true \ No newline at end of file diff --git a/configs/image_scoring/hub_dataset.yml b/configs/image_scoring/hub_dataset.yml new file mode 100644 index 0000000000..bdd2d76473 --- /dev/null +++ b/configs/image_scoring/hub_dataset.yml @@ -0,0 +1,27 @@ +task: image_regression +base_model: google/vit-base-patch16-224 +project_name: autotrain-cats-vs-dogs-finetuned +log: tensorboard +backend: local + +data: + path: cats_vs_dogs + train_split: train + valid_split: null + column_mapping: + image_column: image + target_column: labels + +params: + epochs: 2 + batch_size: 4 + lr: 2e-5 + optimizer: adamw_torch + scheduler: linear + gradient_accumulation: 1 + mixed_precision: fp16 + +hub: + username: ${HF_USERNAME} + token: ${HF_TOKEN} + push_to_hub: true \ No newline at end of file diff --git a/configs/image_scoring/local.yml b/configs/image_scoring/local.yml new file mode 100644 index 0000000000..377cf227a7 --- /dev/null +++ b/configs/image_scoring/local.yml @@ -0,0 +1,28 @@ +task: image_regression +base_model: google/vit-base-patch16-224 +project_name: autotrain-image-regression-model +log: tensorboard +backend: local + +data: + path: data/ + train_split: train # this folder inside data/ will be used for training, it contains the images and metadata.jsonl + valid_split: valid # this folder inside data/ will be used for validation, it contains the images and metadata.jsonl. can be set to null + # column mapping should not be changed for local datasets + column_mapping: + image_column: image + target_column: target + +params: + epochs: 2 + batch_size: 4 + lr: 2e-5 + optimizer: adamw_torch + scheduler: linear + gradient_accumulation: 1 + mixed_precision: fp16 + +hub: + username: ${HF_USERNAME} + token: ${HF_TOKEN} + push_to_hub: true \ No newline at end of file diff --git a/docs/source/_toctree.yml b/docs/source/_toctree.yml index 9b599f265a..839751992c 100644 --- a/docs/source/_toctree.yml +++ b/docs/source/_toctree.yml @@ -35,6 +35,8 @@ title: LLM Finetuning - local: image_classification title: Image Classification + - local: image_regression + title: Image Scoring/Regression - local: object_detection title: Object Detection - local: dreambooth @@ -53,6 +55,8 @@ title: LLM Finetuning - local: image_classification_params title: Image Classification + - local: image_regression_params + title: Image Scoring/Regression - local: object_detection_params title: Object Detection - local: dreambooth_params diff --git a/docs/source/image_regression.mdx b/docs/source/image_regression.mdx new file mode 100644 index 0000000000..d6fb25fc38 --- /dev/null +++ b/docs/source/image_regression.mdx @@ -0,0 +1,58 @@ +# Image Scoring/Regression + +Image scoring is a form of supervised learning where a model is trained to predict a +score or value for an image. AutoTrain simplifies the process, enabling you to train a +state-of-the-art image scoring model by simply uploading labeled example images. + + +## Preparing your data + +To ensure your image scoring model trains effectively, follow these guidelines for preparing your data: + + +### Organizing Images + + +Prepare a zip file containing your images and metadata.jsonl. + + +``` +Archive.zip +├── 0001.png +├── 0002.png +├── 0003.png +├── . +├── . +├── . +└── metadata.jsonl +``` + +Example for `metadata.jsonl`: + +``` +{"file_name": "0001.png", "target": 0.5} +{"file_name": "0002.png", "target": 0.7} +{"file_name": "0003.png", "target": 0.3} +``` + +Please note that metadata.jsonl should contain the `file_name` and the `target` value for each image. + + +### Image Requirements + +- Format: Ensure all images are in JPEG, JPG, or PNG format. + +- Quantity: Include at least 5 images to provide the model with sufficient examples for learning. + +- Exclusivity: The zip file should exclusively contain images and metadata.jsonl. +No additional files or nested folders should be included. + + +Some points to keep in mind: + +- The images must be jpeg, jpg or png. +- There should be at least 5 images per class. +- There must not be any other files in the zip file. +- There must not be any other folders inside the zip folder. + +When train.zip is decompressed, it creates no folders: only images and metadata.jsonl. \ No newline at end of file diff --git a/docs/source/image_regression_params.mdx b/docs/source/image_regression_params.mdx new file mode 100644 index 0000000000..8434a0ab9d --- /dev/null +++ b/docs/source/image_regression_params.mdx @@ -0,0 +1,3 @@ +# Image Scoring/Regression Parameters + +The Parameters for image scoring/regression are same as the parameters for image classification. diff --git a/docs/source/object_detection.mdx b/docs/source/object_detection.mdx index b8e6814d84..a05d1bdc29 100644 --- a/docs/source/object_detection.mdx +++ b/docs/source/object_detection.mdx @@ -50,10 +50,8 @@ No additional files or nested folders should be included. Some points to keep in mind: -- The zip file should contain multiple folders (the classes), each folder should contain images of a single class. -- The name of the folder should be the name of the class. - The images must be jpeg, jpg or png. -- There should be at least 5 images per class. +- There should be at least 5 images per split. - There must not be any other files in the zip file. - There must not be any other folders inside the zip folder. diff --git a/src/autotrain/app/api_routes.py b/src/autotrain/app/api_routes.py index f612605be7..c1db5b0a89 100644 --- a/src/autotrain/app/api_routes.py +++ b/src/autotrain/app/api_routes.py @@ -13,6 +13,7 @@ from autotrain.trainers.clm.params import LLMTrainingParams from autotrain.trainers.dreambooth.params import DreamBoothTrainingParams from autotrain.trainers.image_classification.params import ImageClassificationParams +from autotrain.trainers.image_regression.params import ImageRegressionParams from autotrain.trainers.sent_transformers.params import SentenceTransformersParams from autotrain.trainers.seq2seq.params import Seq2SeqParams from autotrain.trainers.tabular.params import TabularParams @@ -86,6 +87,7 @@ def create_api_base_model(base_class, class_name): TextRegressionParamsAPI = create_api_base_model(TextRegressionParams, "TextRegressionParamsAPI") TokenClassificationParamsAPI = create_api_base_model(TokenClassificationParams, "TokenClassificationParamsAPI") SentenceTransformersParamsAPI = create_api_base_model(SentenceTransformersParams, "SentenceTransformersParamsAPI") +ImageRegressionParamsAPI = create_api_base_model(ImageRegressionParams, "ImageRegressionParamsAPI") class LLMSFTColumnMapping(BaseModel): @@ -122,6 +124,11 @@ class ImageClassificationColumnMapping(BaseModel): target_column: str +class ImageRegressionColumnMapping(BaseModel): + image_column: str + target_column: str + + class Seq2SeqColumnMapping(BaseModel): text_column: str target_column: str @@ -201,6 +208,7 @@ class APICreateProjectModel(BaseModel): "text-regression", "tabular-classification", "tabular-regression", + "image-regression", ] base_model: str hardware: Literal[ @@ -232,6 +240,7 @@ class APICreateProjectModel(BaseModel): TextClassificationParamsAPI, TextRegressionParamsAPI, TokenClassificationParamsAPI, + ImageRegressionParamsAPI, ] username: str column_mapping: Optional[ @@ -254,6 +263,7 @@ class APICreateProjectModel(BaseModel): STPairScoreColumnMapping, STTripletColumnMapping, STQAColumnMapping, + ImageRegressionColumnMapping, ] ] = None hub_dataset: str @@ -408,6 +418,14 @@ def validate_column_mapping(cls, values): if not values.get("column_mapping").get("sentence2_column"): raise ValueError("sentence2_column is required for st:qa") values["column_mapping"] = STQAColumnMapping(**values["column_mapping"]) + elif values.get("task") == "image-regression": + if not values.get("column_mapping"): + raise ValueError("column_mapping is required for image-regression") + if not values.get("column_mapping").get("image_column"): + raise ValueError("image_column is required for image-regression") + if not values.get("column_mapping").get("target_column"): + raise ValueError("target_column is required for image-regression") + values["column_mapping"] = ImageRegressionColumnMapping(**values["column_mapping"]) return values @model_validator(mode="before") @@ -441,6 +459,8 @@ def validate_params(cls, values): values["params"] = TokenClassificationParamsAPI(**values["params"]) elif values.get("task").startswith("st:"): values["params"] = SentenceTransformersParamsAPI(**values["params"]) + elif values.get("task") == "image-regression": + values["params"] = ImageRegressionParamsAPI(**values["params"]) return values diff --git a/src/autotrain/app/models.py b/src/autotrain/app/models.py index fe5d2e5f5a..a6e30544fb 100644 --- a/src/autotrain/app/models.py +++ b/src/autotrain/app/models.py @@ -316,6 +316,7 @@ def fetch_models(): _mc["text-classification"] = _fetch_text_classification_models() _mc["llm"] = _fetch_llm_models() _mc["image-classification"] = _fetch_image_classification_models() + _mc["image-regression"] = _fetch_image_classification_models() _mc["dreambooth"] = _fetch_dreambooth_models() _mc["seq2seq"] = _fetch_seq2seq_models() _mc["token-classification"] = _fetch_token_classification_models() diff --git a/src/autotrain/app/params.py b/src/autotrain/app/params.py index edbdbfef54..0b822c1610 100644 --- a/src/autotrain/app/params.py +++ b/src/autotrain/app/params.py @@ -5,6 +5,7 @@ from autotrain.trainers.clm.params import LLMTrainingParams from autotrain.trainers.dreambooth.params import DreamBoothTrainingParams from autotrain.trainers.image_classification.params import ImageClassificationParams +from autotrain.trainers.image_regression.params import ImageRegressionParams from autotrain.trainers.object_detection.params import ObjectDetectionParams from autotrain.trainers.sent_transformers.params import SentenceTransformersParams from autotrain.trainers.seq2seq.params import Seq2SeqParams @@ -126,6 +127,10 @@ mixed_precision="fp16", log="tensorboard", ).model_dump() +PARAMS["image-regression"] = ImageRegressionParams( + mixed_precision="fp16", + log="tensorboard", +).model_dump() @dataclass @@ -168,6 +173,8 @@ def munge(self): return self._munge_params_text_reg() elif self.task.startswith("st:"): return self._munge_params_sent_transformers() + elif self.task == "image-regression": + return self._munge_params_img_reg() else: raise ValueError(f"Unknown task: {self.task}") @@ -315,6 +322,22 @@ def _munge_params_img_clf(self): return ImageClassificationParams(**_params) + def _munge_params_img_reg(self): + _params = self._munge_common_params() + _params["model"] = self.base_model + _params["log"] = "tensorboard" + if not self.using_hub_dataset: + _params["image_column"] = "autotrain_image" + _params["target_column"] = "autotrain_label" + _params["valid_split"] = "validation" + else: + _params["image_column"] = self.column_mapping.get("image" if not self.api else "image_column", "image") + _params["target_column"] = self.column_mapping.get("target" if not self.api else "target_column", "target") + _params["train_split"] = self.train_split + _params["valid_split"] = self.valid_split + + return ImageRegressionParams(**_params) + def _munge_params_img_obj_det(self): _params = self._munge_common_params() _params["model"] = self.base_model @@ -511,6 +534,20 @@ def get_task_params(task, param_type): "early_stopping_threshold", ] task_params = {k: v for k, v in task_params.items() if k not in more_hidden_params} + if task == "image-regression" and param_type == "basic": + more_hidden_params = [ + "warmup_ratio", + "weight_decay", + "max_grad_norm", + "seed", + "logging_steps", + "auto_find_batch_size", + "save_total_limit", + "evaluation_strategy", + "early_stopping_patience", + "early_stopping_threshold", + ] + task_params = {k: v for k, v in task_params.items() if k not in more_hidden_params} if task == "image-object-detection" and param_type == "basic": more_hidden_params = [ "warmup_ratio", diff --git a/src/autotrain/app/templates/index.html b/src/autotrain/app/templates/index.html index 33995a6f91..397df5a410 100644 --- a/src/autotrain/app/templates/index.html +++ b/src/autotrain/app/templates/index.html @@ -76,6 +76,10 @@ fields = ['image', 'label']; fieldNames = ['image', 'label']; break; + case 'image-regression': + fields = ['image', 'label']; + fieldNames = ['image', 'target']; + break; case 'image-object-detection': fields = ['image', 'objects']; fieldNames = ['image', 'objects']; @@ -200,6 +204,7 @@ + diff --git a/src/autotrain/app/ui_routes.py b/src/autotrain/app/ui_routes.py index 60d922894c..b9031564cb 100644 --- a/src/autotrain/app/ui_routes.py +++ b/src/autotrain/app/ui_routes.py @@ -21,6 +21,7 @@ AutoTrainDataset, AutoTrainDreamboothDataset, AutoTrainImageClassificationDataset, + AutoTrainImageRegressionDataset, AutoTrainObjectDetectionDataset, ) from autotrain.help import get_app_help @@ -437,6 +438,8 @@ async def fetch_model_choices( hub_models = MODEL_CHOICE["text-regression"] elif task == "image-object-detection": hub_models = MODEL_CHOICE["image-object-detection"] + elif task == "image-regression": + hub_models = MODEL_CHOICE["image-regression"] else: raise NotImplementedError @@ -539,6 +542,16 @@ async def handle_form( percent_valid=None, # TODO: add to UI local=hardware.lower() == "local-ui", ) + elif task == "image-regression": + dset = AutoTrainImageRegressionDataset( + train_data=training_files[0], + token=token, + project_name=project_name, + username=autotrain_user, + valid_data=validation_files[0] if validation_files else None, + percent_valid=None, # TODO: add to UI + local=hardware.lower() == "local-ui", + ) elif task == "image-object-detection": dset = AutoTrainObjectDetectionDataset( train_data=training_files[0], diff --git a/src/autotrain/backends/base.py b/src/autotrain/backends/base.py index 1c91405ac4..fb4475a863 100644 --- a/src/autotrain/backends/base.py +++ b/src/autotrain/backends/base.py @@ -6,6 +6,7 @@ from autotrain.trainers.dreambooth.params import DreamBoothTrainingParams from autotrain.trainers.generic.params import GenericParams from autotrain.trainers.image_classification.params import ImageClassificationParams +from autotrain.trainers.image_regression.params import ImageRegressionParams from autotrain.trainers.object_detection.params import ObjectDetectionParams from autotrain.trainers.sent_transformers.params import SentenceTransformersParams from autotrain.trainers.seq2seq.params import Seq2SeqParams @@ -68,6 +69,7 @@ class BaseBackend: TextRegressionParams, ObjectDetectionParams, SentenceTransformersParams, + ImageRegressionParams, ] backend: str @@ -110,6 +112,8 @@ def __post_init__(self): self.task_id = 29 elif isinstance(self.params, SentenceTransformersParams): self.task_id = 30 + elif isinstance(self.params, ImageRegressionParams): + self.task_id = 24 else: raise NotImplementedError diff --git a/src/autotrain/cli/autotrain.py b/src/autotrain/cli/autotrain.py index 199ab69ffe..34292e975b 100644 --- a/src/autotrain/cli/autotrain.py +++ b/src/autotrain/cli/autotrain.py @@ -5,6 +5,7 @@ from autotrain.cli.run_app import RunAutoTrainAppCommand from autotrain.cli.run_dreambooth import RunAutoTrainDreamboothCommand from autotrain.cli.run_image_classification import RunAutoTrainImageClassificationCommand +from autotrain.cli.run_image_regression import RunAutoTrainImageRegressionCommand from autotrain.cli.run_llm import RunAutoTrainLLMCommand from autotrain.cli.run_object_detection import RunAutoTrainObjectDetectionCommand from autotrain.cli.run_sent_tranformers import RunAutoTrainSentenceTransformersCommand @@ -45,6 +46,7 @@ def main(): RunAutoTrainTextRegressionCommand.register_subcommand(commands_parser) RunAutoTrainObjectDetectionCommand.register_subcommand(commands_parser) RunAutoTrainSentenceTransformersCommand.register_subcommand(commands_parser) + RunAutoTrainImageRegressionCommand.register_subcommand(commands_parser) args = parser.parse_args() diff --git a/src/autotrain/cli/run_image_regression.py b/src/autotrain/cli/run_image_regression.py new file mode 100644 index 0000000000..4deb09684f --- /dev/null +++ b/src/autotrain/cli/run_image_regression.py @@ -0,0 +1,104 @@ +from argparse import ArgumentParser + +from autotrain import logger +from autotrain.cli.utils import get_field_info, img_reg_munge_data +from autotrain.project import AutoTrainProject +from autotrain.trainers.image_regression.params import ImageRegressionParams + +from . import BaseAutoTrainCommand + + +def run_image_regression_command_factory(args): + return RunAutoTrainImageRegressionCommand(args) + + +class RunAutoTrainImageRegressionCommand(BaseAutoTrainCommand): + @staticmethod + def register_subcommand(parser: ArgumentParser): + arg_list = get_field_info(ImageRegressionParams) + arg_list = [ + { + "arg": "--train", + "help": "Command to train the model", + "required": False, + "action": "store_true", + }, + { + "arg": "--deploy", + "help": "Command to deploy the model (limited availability)", + "required": False, + "action": "store_true", + }, + { + "arg": "--inference", + "help": "Command to run inference (limited availability)", + "required": False, + "action": "store_true", + }, + ] + arg_list + run_image_regression_parser = parser.add_parser( + "image-regression", description="✨ Run AutoTrain Image Regression" + ) + for arg in arg_list: + if "action" in arg: + run_image_regression_parser.add_argument( + arg["arg"], + help=arg["help"], + required=arg.get("required", False), + action=arg.get("action"), + default=arg.get("default"), + ) + else: + run_image_regression_parser.add_argument( + arg["arg"], + help=arg["help"], + required=arg.get("required", False), + type=arg.get("type"), + default=arg.get("default"), + choices=arg.get("choices"), + ) + run_image_regression_parser.set_defaults(func=run_image_regression_command_factory) + + def __init__(self, args): + self.args = args + + store_true_arg_names = [ + "train", + "deploy", + "inference", + "auto_find_batch_size", + "push_to_hub", + ] + for arg_name in store_true_arg_names: + if getattr(self.args, arg_name) is None: + setattr(self.args, arg_name, False) + + if self.args.train: + if self.args.project_name is None: + raise ValueError("Project name must be specified") + if self.args.data_path is None: + raise ValueError("Data path must be specified") + if self.args.model is None: + raise ValueError("Model must be specified") + if self.args.push_to_hub: + if self.args.username is None: + raise ValueError("Username must be specified for push to hub") + else: + raise ValueError("Must specify --train, --deploy or --inference") + + if self.args.backend.startswith("spaces") or self.args.backend.startswith("ep-"): + if not self.args.push_to_hub: + raise ValueError("Push to hub must be specified for spaces backend") + if self.args.username is None: + raise ValueError("Username must be specified for spaces backend") + if self.args.token is None: + raise ValueError("Token must be specified for spaces backend") + + def run(self): + logger.info("Running Image Regression") + if self.args.train: + params = ImageRegressionParams(**vars(self.args)) + params = img_reg_munge_data(params, local=self.args.backend.startswith("local")) + project = AutoTrainProject(params=params, backend=self.args.backend) + job_id = project.create() + logger.info(f"Job ID: {job_id}") diff --git a/src/autotrain/cli/utils.py b/src/autotrain/cli/utils.py index bde3a20693..e706654eb8 100644 --- a/src/autotrain/cli/utils.py +++ b/src/autotrain/cli/utils.py @@ -6,6 +6,7 @@ AutoTrainDataset, AutoTrainDreamboothDataset, AutoTrainImageClassificationDataset, + AutoTrainImageRegressionDataset, AutoTrainObjectDetectionDataset, ) @@ -525,3 +526,25 @@ def sent_transformers_munge_data(params, local): params.sentence3_column = "autotrain_sentence3" params.target_column = "autotrain_target" return params + + +def img_reg_munge_data(params, local): + train_data_path = f"{params.data_path}/{params.train_split}" + if params.valid_split is not None: + valid_data_path = f"{params.data_path}/{params.valid_split}" + else: + valid_data_path = None + if os.path.isdir(train_data_path): + dset = AutoTrainImageRegressionDataset( + train_data=train_data_path, + valid_data=valid_data_path, + token=params.token, + project_name=params.project_name, + username=params.username, + local=local, + ) + params.data_path = dset.prepare() + params.valid_split = "validation" + params.image_column = "autotrain_image" + params.target_column = "autotrain_label" + return params diff --git a/src/autotrain/commands.py b/src/autotrain/commands.py index d55e9bad85..d1adfadd4e 100644 --- a/src/autotrain/commands.py +++ b/src/autotrain/commands.py @@ -8,6 +8,7 @@ from autotrain.trainers.dreambooth.params import DreamBoothTrainingParams from autotrain.trainers.generic.params import GenericParams from autotrain.trainers.image_classification.params import ImageClassificationParams +from autotrain.trainers.image_regression.params import ImageRegressionParams from autotrain.trainers.object_detection.params import ObjectDetectionParams from autotrain.trainers.sent_transformers.params import SentenceTransformersParams from autotrain.trainers.seq2seq.params import Seq2SeqParams @@ -250,7 +251,11 @@ def launch_command(params): os.path.join(params.project_name, "training_params.json"), ] ) - elif isinstance(params, ImageClassificationParams) or isinstance(params, ObjectDetectionParams): + elif ( + isinstance(params, ImageClassificationParams) + or isinstance(params, ObjectDetectionParams) + or isinstance(params, ImageRegressionParams) + ): if num_gpus == 0: cmd = [ "accelerate", @@ -295,6 +300,15 @@ def launch_command(params): os.path.join(params.project_name, "training_params.json"), ] ) + elif isinstance(params, ImageRegressionParams): + cmd.extend( + [ + "-m", + "autotrain.trainers.image_regression", + "--training_config", + os.path.join(params.project_name, "training_params.json"), + ] + ) else: cmd.extend( [ diff --git a/src/autotrain/dataset.py b/src/autotrain/dataset.py index 6dbfef09cb..661b971382 100644 --- a/src/autotrain/dataset.py +++ b/src/autotrain/dataset.py @@ -24,7 +24,11 @@ TextSingleColumnRegressionPreprocessor, TextTokenClassificationPreprocessor, ) -from autotrain.preprocessor.vision import ImageClassificationPreprocessor, ObjectDetectionPreprocessor +from autotrain.preprocessor.vision import ( + ImageClassificationPreprocessor, + ImageRegressionPreprocessor, + ObjectDetectionPreprocessor, +) def remove_non_image_files(folder): @@ -232,6 +236,82 @@ def prepare(self): return preprocessor.prepare() +@dataclass +class AutoTrainImageRegressionDataset: + train_data: str + token: str + project_name: str + username: str + valid_data: Optional[str] = None + percent_valid: Optional[float] = None + local: bool = False + + def __str__(self) -> str: + info = f"Dataset: {self.project_name} ({self.task})\n" + info += f"Train data: {self.train_data}\n" + info += f"Valid data: {self.valid_data}\n" + return info + + def __post_init__(self): + self.task = "image_single_column_regression" + if not self.valid_data and self.percent_valid is None: + self.percent_valid = 0.2 + elif self.valid_data and self.percent_valid is not None: + raise ValueError("You can only specify one of valid_data or percent_valid") + elif self.valid_data: + self.percent_valid = 0.0 + + def prepare(self): + valid_dir = None + if not isinstance(self.train_data, str): + cache_dir = os.environ.get("HF_HOME") + if not cache_dir: + cache_dir = os.path.join(os.path.expanduser("~"), ".cache", "huggingface") + + random_uuid = uuid.uuid4() + train_dir = os.path.join(cache_dir, "autotrain", str(random_uuid)) + os.makedirs(train_dir, exist_ok=True) + self.train_data.seek(0) + content = self.train_data.read() + bytes_io = io.BytesIO(content) + + zip_ref = zipfile.ZipFile(bytes_io, "r") + zip_ref.extractall(train_dir) + # remove the __MACOSX directory + macosx_dir = os.path.join(train_dir, "__MACOSX") + if os.path.exists(macosx_dir): + os.system(f"rm -rf {macosx_dir}") + remove_non_image_files(train_dir) + if self.valid_data: + random_uuid = uuid.uuid4() + valid_dir = os.path.join(cache_dir, "autotrain", str(random_uuid)) + os.makedirs(valid_dir, exist_ok=True) + self.valid_data.seek(0) + content = self.valid_data.read() + bytes_io = io.BytesIO(content) + zip_ref = zipfile.ZipFile(bytes_io, "r") + zip_ref.extractall(valid_dir) + # remove the __MACOSX directory + macosx_dir = os.path.join(valid_dir, "__MACOSX") + if os.path.exists(macosx_dir): + os.system(f"rm -rf {macosx_dir}") + remove_non_image_files(valid_dir) + else: + train_dir = self.train_data + if self.valid_data: + valid_dir = self.valid_data + + preprocessor = ImageRegressionPreprocessor( + train_data=train_dir, + valid_data=valid_dir, + token=self.token, + project_name=self.project_name, + username=self.username, + local=self.local, + ) + return preprocessor.prepare() + + @dataclass class AutoTrainDataset: train_data: List[str] diff --git a/src/autotrain/parser.py b/src/autotrain/parser.py index b625a67d04..b1f13861a5 100644 --- a/src/autotrain/parser.py +++ b/src/autotrain/parser.py @@ -9,6 +9,7 @@ dreambooth_munge_data, img_clf_munge_data, img_obj_detect_munge_data, + img_reg_munge_data, llm_munge_data, sent_transformers_munge_data, seq2seq_munge_data, @@ -22,6 +23,7 @@ from autotrain.trainers.clm.params import LLMTrainingParams from autotrain.trainers.dreambooth.params import DreamBoothTrainingParams from autotrain.trainers.image_classification.params import ImageClassificationParams +from autotrain.trainers.image_regression.params import ImageRegressionParams from autotrain.trainers.object_detection.params import ObjectDetectionParams from autotrain.trainers.sent_transformers.params import SentenceTransformersParams from autotrain.trainers.seq2seq.params import Seq2SeqParams @@ -59,6 +61,7 @@ def __post_init__(self): "text_single_column_regression": TextRegressionParams, "text_token_classification": TokenClassificationParams, "sentence_transformers": SentenceTransformersParams, + "image_single_column_regression": ImageRegressionParams, } self.munge_data_map = { "lm_training": llm_munge_data, @@ -71,6 +74,7 @@ def __post_init__(self): "text_token_classification": token_clf_munge_data, "text_single_column_regression": text_reg_munge_data, "sentence_transformers": sent_transformers_munge_data, + "image_single_column_regression": img_reg_munge_data, } self.task_aliases = { "llm": "lm_training", @@ -110,6 +114,11 @@ def __post_init__(self): "sentence-transformers:pair_score": "sentence_transformers", "sentence-transformers:triplet": "sentence_transformers", "sentence-transformers:qa": "sentence_transformers", + "image_single_column_regression": "image_single_column_regression", + "image-single-column-regression": "image_single_column_regression", + "image_regression": "image_single_column_regression", + "image-regression": "image_single_column_regression", + "image-scoring": "image_single_column_regression", } task = self.config.get("task") self.task = self.task_aliases.get(task, task) diff --git a/src/autotrain/preprocessor/vision.py b/src/autotrain/preprocessor/vision.py index 62bc506739..4c2c7077f8 100644 --- a/src/autotrain/preprocessor/vision.py +++ b/src/autotrain/preprocessor/vision.py @@ -347,3 +347,153 @@ def prepare(self): if self.local: return f"{self.project_name}/autotrain-data" return f"{self.username}/autotrain-data-{self.project_name}" + + +@dataclass +class ImageRegressionPreprocessor: + train_data: str + username: str + project_name: str + token: str + valid_data: Optional[str] = None + test_size: Optional[float] = 0.2 + seed: Optional[int] = 42 + local: Optional[bool] = False + + @staticmethod + def _process_metadata(data_path): + metadata = pd.read_json(os.path.join(data_path, "metadata.jsonl"), lines=True) + # make sure that the metadata.jsonl file contains the required columns: file_name, target + if "file_name" not in metadata.columns or "target" not in metadata.columns: + raise ValueError(f"{data_path}/metadata.jsonl should contain 'file_name' and 'target' columns.") + + # keep only file_name and target columns + metadata = metadata[["file_name", "target"]] + return metadata + + def __post_init__(self): + # Check if train data path exists + if not os.path.exists(self.train_data): + raise ValueError(f"{self.train_data} does not exist.") + + # check if self.train_data contains at least 5 image files in jpeg, png or jpg format only + train_image_files = [f for f in os.listdir(self.train_data) if f.endswith(ALLOWED_EXTENSIONS)] + if len(train_image_files) < 5: + raise ValueError(f"{self.train_data} should contain at least 5 jpeg, png or jpg files.") + + # check if self.train_data contains a metadata.jsonl file + if "metadata.jsonl" not in os.listdir(self.train_data): + raise ValueError(f"{self.train_data} should contain a metadata.jsonl file.") + + # Check if valid data path exists + if self.valid_data: + if not os.path.exists(self.valid_data): + raise ValueError(f"{self.valid_data} does not exist.") + + # check if self.valid_data contains at least 5 image files in jpeg, png or jpg format only + valid_image_files = [f for f in os.listdir(self.valid_data) if f.endswith(ALLOWED_EXTENSIONS)] + if len(valid_image_files) < 5: + raise ValueError(f"{self.valid_data} should contain at least 5 jpeg, png or jpg files.") + + # check if self.valid_data contains a metadata.jsonl file + if "metadata.jsonl" not in os.listdir(self.valid_data): + raise ValueError(f"{self.valid_data} should contain a metadata.jsonl file.") + + def split(self, df): + train_df, valid_df = train_test_split( + df, + test_size=self.test_size, + random_state=self.seed, + ) + train_df = train_df.reset_index(drop=True) + valid_df = valid_df.reset_index(drop=True) + return train_df, valid_df + + def prepare(self): + random_uuid = uuid.uuid4() + cache_dir = os.environ.get("HF_HOME") + if not cache_dir: + cache_dir = os.path.join(os.path.expanduser("~"), ".cache", "huggingface") + data_dir = os.path.join(cache_dir, "autotrain", str(random_uuid)) + + if self.valid_data: + shutil.copytree(self.train_data, os.path.join(data_dir, "train")) + shutil.copytree(self.valid_data, os.path.join(data_dir, "validation")) + + train_metadata = self._process_metadata(os.path.join(data_dir, "train")) + valid_metadata = self._process_metadata(os.path.join(data_dir, "validation")) + + train_metadata.to_json(os.path.join(data_dir, "train", "metadata.jsonl"), orient="records", lines=True) + valid_metadata.to_json( + os.path.join(data_dir, "validation", "metadata.jsonl"), orient="records", lines=True + ) + + dataset = load_dataset("imagefolder", data_dir=data_dir) + dataset = dataset.rename_columns( + { + "image": "autotrain_image", + "target": "autotrain_label", + } + ) + + if self.local: + dataset.save_to_disk(f"{self.project_name}/autotrain-data") + else: + dataset.push_to_hub( + f"{self.username}/autotrain-data-{self.project_name}", + private=True, + token=self.token, + ) + else: + metadata = pd.read_json(os.path.join(self.train_data, "metadata.jsonl"), lines=True) + train_df, valid_df = self.split(metadata) + + # create train and validation folders + os.makedirs(os.path.join(data_dir, "train"), exist_ok=True) + os.makedirs(os.path.join(data_dir, "validation"), exist_ok=True) + + # move images to train and validation folders + for row in train_df.iterrows(): + shutil.copy( + os.path.join(self.train_data, row[1]["file_name"]), + os.path.join(data_dir, "train", row[1]["file_name"]), + ) + + for row in valid_df.iterrows(): + shutil.copy( + os.path.join(self.train_data, row[1]["file_name"]), + os.path.join(data_dir, "validation", row[1]["file_name"]), + ) + + # save metadata.jsonl file to train and validation folders + train_df.to_json(os.path.join(data_dir, "train", "metadata.jsonl"), orient="records", lines=True) + valid_df.to_json(os.path.join(data_dir, "validation", "metadata.jsonl"), orient="records", lines=True) + + train_metadata = self._process_metadata(os.path.join(data_dir, "train")) + valid_metadata = self._process_metadata(os.path.join(data_dir, "validation")) + + train_metadata.to_json(os.path.join(data_dir, "train", "metadata.jsonl"), orient="records", lines=True) + valid_metadata.to_json( + os.path.join(data_dir, "validation", "metadata.jsonl"), orient="records", lines=True + ) + + dataset = load_dataset("imagefolder", data_dir=data_dir) + dataset = dataset.rename_columns( + { + "image": "autotrain_image", + "target": "autotrain_label", + } + ) + + if self.local: + dataset.save_to_disk(f"{self.project_name}/autotrain-data") + else: + dataset.push_to_hub( + f"{self.username}/autotrain-data-{self.project_name}", + private=True, + token=self.token, + ) + + if self.local: + return f"{self.project_name}/autotrain-data" + return f"{self.username}/autotrain-data-{self.project_name}" diff --git a/src/autotrain/project.py b/src/autotrain/project.py index 744b2cdcb7..80f3d5b1cb 100644 --- a/src/autotrain/project.py +++ b/src/autotrain/project.py @@ -14,6 +14,7 @@ from autotrain.trainers.clm.params import LLMTrainingParams from autotrain.trainers.dreambooth.params import DreamBoothTrainingParams from autotrain.trainers.image_classification.params import ImageClassificationParams +from autotrain.trainers.image_regression.params import ImageRegressionParams from autotrain.trainers.object_detection.params import ObjectDetectionParams from autotrain.trainers.sent_transformers.params import SentenceTransformersParams from autotrain.trainers.seq2seq.params import Seq2SeqParams @@ -38,6 +39,7 @@ class AutoTrainProject: ObjectDetectionParams, TokenClassificationParams, SentenceTransformersParams, + ImageRegressionParams, ] ], LLMTrainingParams, @@ -50,6 +52,7 @@ class AutoTrainProject: ObjectDetectionParams, TokenClassificationParams, SentenceTransformersParams, + ImageRegressionParams, ] backend: str diff --git a/src/autotrain/trainers/image_regression/__init__.py b/src/autotrain/trainers/image_regression/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/autotrain/trainers/image_regression/__main__.py b/src/autotrain/trainers/image_regression/__main__.py new file mode 100644 index 0000000000..78b81acf17 --- /dev/null +++ b/src/autotrain/trainers/image_regression/__main__.py @@ -0,0 +1,222 @@ +import argparse +import json + +from accelerate.state import PartialState +from datasets import load_dataset, load_from_disk +from huggingface_hub import HfApi +from transformers import ( + AutoConfig, + AutoImageProcessor, + AutoModelForImageClassification, + EarlyStoppingCallback, + Trainer, + TrainingArguments, +) +from transformers.trainer_callback import PrinterCallback + +from autotrain import logger +from autotrain.trainers.common import ( + ALLOW_REMOTE_CODE, + LossLoggingCallback, + TrainStartCallback, + UploadLogs, + monitor, + pause_space, + remove_autotrain_data, + save_training_params, +) +from autotrain.trainers.image_regression import utils +from autotrain.trainers.image_regression.params import ImageRegressionParams + + +def parse_args(): + # get training_config.json from the end user + parser = argparse.ArgumentParser() + parser.add_argument("--training_config", type=str, required=True) + return parser.parse_args() + + +@monitor +def train(config): + if isinstance(config, dict): + config = ImageRegressionParams(**config) + + valid_data = None + if config.data_path == f"{config.project_name}/autotrain-data": + train_data = load_from_disk(config.data_path)[config.train_split] + else: + if ":" in config.train_split: + dataset_config_name, split = config.train_split.split(":") + train_data = load_dataset( + config.data_path, + name=dataset_config_name, + split=split, + token=config.token, + ) + else: + train_data = load_dataset( + config.data_path, + split=config.train_split, + token=config.token, + ) + + if config.valid_split is not None: + if config.data_path == f"{config.project_name}/autotrain-data": + valid_data = load_from_disk(config.data_path)[config.valid_split] + else: + if ":" in config.valid_split: + dataset_config_name, split = config.valid_split.split(":") + valid_data = load_dataset( + config.data_path, + name=dataset_config_name, + split=split, + token=config.token, + ) + else: + valid_data = load_dataset( + config.data_path, + split=config.valid_split, + token=config.token, + ) + + logger.info(f"Train data: {train_data}") + logger.info(f"Valid data: {valid_data}") + + model_config = AutoConfig.from_pretrained( + config.model, + num_labels=1, + trust_remote_code=ALLOW_REMOTE_CODE, + token=config.token, + ) + model_config._num_labels = 1 + label2id = {"target": 0} + model_config.label2id = label2id + model_config.id2label = {v: k for k, v in label2id.items()} + + try: + model = AutoModelForImageClassification.from_pretrained( + config.model, + config=model_config, + trust_remote_code=ALLOW_REMOTE_CODE, + token=config.token, + ignore_mismatched_sizes=True, + ) + except OSError: + model = AutoModelForImageClassification.from_pretrained( + config.model, + config=model_config, + from_tf=True, + trust_remote_code=ALLOW_REMOTE_CODE, + token=config.token, + ignore_mismatched_sizes=True, + ) + + image_processor = AutoImageProcessor.from_pretrained( + config.model, + token=config.token, + trust_remote_code=ALLOW_REMOTE_CODE, + ) + train_data, valid_data = utils.process_data(train_data, valid_data, image_processor, config) + + if config.logging_steps == -1: + if config.valid_split is not None: + logging_steps = int(0.2 * len(valid_data) / config.batch_size) + else: + logging_steps = int(0.2 * len(train_data) / config.batch_size) + if logging_steps == 0: + logging_steps = 1 + if logging_steps > 25: + logging_steps = 25 + config.logging_steps = logging_steps + else: + logging_steps = config.logging_steps + + logger.info(f"Logging steps: {logging_steps}") + + training_args = dict( + output_dir=config.project_name, + per_device_train_batch_size=config.batch_size, + per_device_eval_batch_size=2 * config.batch_size, + learning_rate=config.lr, + num_train_epochs=config.epochs, + evaluation_strategy=config.evaluation_strategy if config.valid_split is not None else "no", + logging_steps=logging_steps, + save_total_limit=config.save_total_limit, + save_strategy=config.evaluation_strategy if config.valid_split is not None else "no", + gradient_accumulation_steps=config.gradient_accumulation, + report_to=config.log, + auto_find_batch_size=config.auto_find_batch_size, + lr_scheduler_type=config.scheduler, + optim=config.optimizer, + warmup_ratio=config.warmup_ratio, + weight_decay=config.weight_decay, + max_grad_norm=config.max_grad_norm, + push_to_hub=False, + load_best_model_at_end=True if config.valid_split is not None else False, + ddp_find_unused_parameters=False, + ) + + if config.mixed_precision == "fp16": + training_args["fp16"] = True + if config.mixed_precision == "bf16": + training_args["bf16"] = True + + if config.valid_split is not None: + early_stop = EarlyStoppingCallback( + early_stopping_patience=config.early_stopping_patience, + early_stopping_threshold=config.early_stopping_threshold, + ) + callbacks_to_use = [early_stop] + else: + callbacks_to_use = [] + + callbacks_to_use.extend([UploadLogs(config=config), LossLoggingCallback(), TrainStartCallback()]) + + args = TrainingArguments(**training_args) + trainer_args = dict( + args=args, + model=model, + callbacks=callbacks_to_use, + compute_metrics=utils.image_regression_metrics, + ) + + trainer = Trainer( + **trainer_args, + train_dataset=train_data, + eval_dataset=valid_data, + ) + trainer.remove_callback(PrinterCallback) + trainer.train() + + logger.info("Finished training, saving model...") + trainer.save_model(config.project_name) + image_processor.save_pretrained(config.project_name) + + model_card = utils.create_model_card(config, trainer) + + # save model card to output directory as README.md + with open(f"{config.project_name}/README.md", "w") as f: + f.write(model_card) + + if config.push_to_hub: + if PartialState().process_index == 0: + remove_autotrain_data(config) + save_training_params(config) + logger.info("Pushing model to hub...") + api = HfApi(token=config.token) + api.create_repo( + repo_id=f"{config.username}/{config.project_name}", repo_type="model", private=True, exist_ok=True + ) + api.upload_folder( + folder_path=config.project_name, repo_id=f"{config.username}/{config.project_name}", repo_type="model" + ) + + if PartialState().process_index == 0: + pause_space(config) + + +if __name__ == "__main__": + _args = parse_args() + training_config = json.load(open(_args.training_config)) + _config = ImageRegressionParams(**training_config) + train(_config) diff --git a/src/autotrain/trainers/image_regression/dataset.py b/src/autotrain/trainers/image_regression/dataset.py new file mode 100644 index 0000000000..1581f58776 --- /dev/null +++ b/src/autotrain/trainers/image_regression/dataset.py @@ -0,0 +1,24 @@ +import numpy as np +import torch + + +class ImageRegressionDataset: + def __init__(self, data, transforms, config): + self.data = data + self.transforms = transforms + self.config = config + + def __len__(self): + return len(self.data) + + def __getitem__(self, item): + image = self.data[item][self.config.image_column] + target = self.data[item][self.config.target_column] + + image = self.transforms(image=np.array(image.convert("RGB")))["image"] + image = np.transpose(image, (2, 0, 1)).astype(np.float32) + + return { + "pixel_values": torch.tensor(image, dtype=torch.float), + "labels": torch.tensor(target, dtype=torch.float), + } diff --git a/src/autotrain/trainers/image_regression/params.py b/src/autotrain/trainers/image_regression/params.py new file mode 100644 index 0000000000..654c681d3c --- /dev/null +++ b/src/autotrain/trainers/image_regression/params.py @@ -0,0 +1,36 @@ +from typing import Optional + +from pydantic import Field + +from autotrain.trainers.common import AutoTrainParams + + +class ImageRegressionParams(AutoTrainParams): + data_path: str = Field(None, title="Data path") + model: str = Field("google/vit-base-patch16-224", title="Model name") + username: Optional[str] = Field(None, title="Hugging Face Username") + lr: float = Field(5e-5, title="Learning rate") + epochs: int = Field(3, title="Number of training epochs") + batch_size: int = Field(8, title="Training batch size") + warmup_ratio: float = Field(0.1, title="Warmup proportion") + gradient_accumulation: int = Field(1, title="Gradient accumulation steps") + optimizer: str = Field("adamw_torch", title="Optimizer") + scheduler: str = Field("linear", title="Scheduler") + weight_decay: float = Field(0.0, title="Weight decay") + max_grad_norm: float = Field(1.0, title="Max gradient norm") + seed: int = Field(42, title="Seed") + train_split: str = Field("train", title="Train split") + valid_split: Optional[str] = Field(None, title="Validation split") + logging_steps: int = Field(-1, title="Logging steps") + project_name: str = Field("project-name", title="Output directory") + auto_find_batch_size: bool = Field(False, title="Auto find batch size") + mixed_precision: Optional[str] = Field(None, title="fp16, bf16, or None") + save_total_limit: int = Field(1, title="Save total limit") + token: Optional[str] = Field(None, title="Hub Token") + push_to_hub: bool = Field(False, title="Push to hub") + evaluation_strategy: str = Field("epoch", title="Evaluation strategy") + image_column: str = Field("image", title="Image column") + target_column: str = Field("target", title="Target column") + log: str = Field("none", title="Logging using experiment tracking") + early_stopping_patience: int = Field(5, title="Early stopping patience") + early_stopping_threshold: float = Field(0.01, title="Early stopping threshold") diff --git a/src/autotrain/trainers/image_regression/utils.py b/src/autotrain/trainers/image_regression/utils.py new file mode 100644 index 0000000000..423f69fe41 --- /dev/null +++ b/src/autotrain/trainers/image_regression/utils.py @@ -0,0 +1,130 @@ +import os + +import albumentations as A +import numpy as np +from sklearn import metrics + +from autotrain.trainers.image_regression.dataset import ImageRegressionDataset + + +VALID_METRICS = [ + "eval_loss", + "eval_mse", + "eval_mae", + "eval_r2", + "eval_rmse", + "eval_explained_variance", +] + +MODEL_CARD = """ +--- +tags: +- autotrain +- vision +- image-classification +- image-regression{base_model} +widget: +- src: https://huggingface.co/datasets/mishig/sample_images/resolve/main/tiger.jpg + example_title: Tiger +- src: https://huggingface.co/datasets/mishig/sample_images/resolve/main/teapot.jpg + example_title: Teapot +- src: https://huggingface.co/datasets/mishig/sample_images/resolve/main/palace.jpg + example_title: Palace{dataset_tag} +--- + +# Model Trained Using AutoTrain + +- Problem type: Image Regression + +## Validation Metrics + +{validation_metrics} +""" + + +def image_regression_metrics(pred): + raw_predictions, labels = pred + + try: + raw_predictions = [r for preds in raw_predictions for r in preds] + except TypeError as err: + if "numpy.float32" not in str(err): + raise Exception(err) + + pred_dict = {} + metrics_to_calculate = { + "mse": metrics.mean_squared_error, + "mae": metrics.mean_absolute_error, + "r2": metrics.r2_score, + "rmse": lambda y_true, y_pred: np.sqrt(metrics.mean_squared_error(y_true, y_pred)), + "explained_variance": metrics.explained_variance_score, + } + + for key, func in metrics_to_calculate.items(): + try: + pred_dict[key] = float(func(labels, raw_predictions)) + except Exception: + pred_dict[key] = -999 + + return pred_dict + + +def process_data(train_data, valid_data, image_processor, config): + if "shortest_edge" in image_processor.size: + size = image_processor.size["shortest_edge"] + else: + size = (image_processor.size["height"], image_processor.size["width"]) + try: + height, width = size + except TypeError: + height = size + width = size + + train_transforms = A.Compose( + [ + A.RandomResizedCrop(height=height, width=width), + A.RandomRotate90(), + A.HorizontalFlip(p=0.5), + A.RandomBrightnessContrast(p=0.2), + A.Normalize(mean=image_processor.image_mean, std=image_processor.image_std), + ] + ) + + val_transforms = A.Compose( + [ + A.Resize(height=height, width=width), + A.Normalize(mean=image_processor.image_mean, std=image_processor.image_std), + ] + ) + train_data = ImageRegressionDataset(train_data, train_transforms, config) + if valid_data is not None: + valid_data = ImageRegressionDataset(valid_data, val_transforms, config) + return train_data, valid_data + return train_data, None + + +def create_model_card(config, trainer): + if config.valid_split is not None: + eval_scores = trainer.evaluate() + eval_scores = [f"{k[len('eval_'):]}: {v}" for k, v in eval_scores.items() if k in VALID_METRICS] + eval_scores = "\n\n".join(eval_scores) + + else: + eval_scores = "No validation metrics available" + + if config.data_path == f"{config.project_name}/autotrain-data" or os.path.isdir(config.data_path): + dataset_tag = "" + else: + dataset_tag = f"\ndatasets:\n- {config.data_path}" + + if os.path.isdir(config.model): + base_model = "" + else: + base_model = f"\nbase_model: {config.model}" + + model_card = MODEL_CARD.format( + dataset_tag=dataset_tag, + validation_metrics=eval_scores, + base_model=base_model, + ) + return model_card diff --git a/src/autotrain/trainers/text_regression/__main__.py b/src/autotrain/trainers/text_regression/__main__.py index f5765cf5b5..da6a772f4c 100644 --- a/src/autotrain/trainers/text_regression/__main__.py +++ b/src/autotrain/trainers/text_regression/__main__.py @@ -85,7 +85,12 @@ def train(config): token=config.token, ) - model_config = AutoConfig.from_pretrained(config.model, num_labels=1) + model_config = AutoConfig.from_pretrained( + config.model, + num_labels=1, + trust_remote_code=ALLOW_REMOTE_CODE, + token=config.token, + ) model_config._num_labels = 1 label2id = {"target": 0} model_config.label2id = label2id diff --git a/src/autotrain/utils.py b/src/autotrain/utils.py index b362be2e02..09917c1ff7 100644 --- a/src/autotrain/utils.py +++ b/src/autotrain/utils.py @@ -7,6 +7,7 @@ from autotrain.trainers.dreambooth.params import DreamBoothTrainingParams from autotrain.trainers.generic.params import GenericParams from autotrain.trainers.image_classification.params import ImageClassificationParams +from autotrain.trainers.image_regression.params import ImageRegressionParams from autotrain.trainers.object_detection.params import ObjectDetectionParams from autotrain.trainers.sent_transformers.params import SentenceTransformersParams from autotrain.trainers.seq2seq.params import Seq2SeqParams @@ -45,6 +46,8 @@ def run_training(params, task_id, local=False, wait=False): params = ObjectDetectionParams(**params) elif task_id == 30: params = SentenceTransformersParams(**params) + elif task_id == 24: + params = ImageRegressionParams(**params) else: raise NotImplementedError