deltpy 1.4.0__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.
Files changed (64) hide show
  1. delt/__init__.py +31 -0
  2. delt/common/__init__.py +21 -0
  3. delt/common/experimental.py +46 -0
  4. delt/common/io.py +24 -0
  5. delt/common/iterables.py +67 -0
  6. delt/common/llm.py +94 -0
  7. delt/common/logging.py +81 -0
  8. delt/common/prompting.py +41 -0
  9. delt/common/utils.py +1 -0
  10. delt/encoders/__init__.py +52 -0
  11. delt/encoders/audio/__init__.py +6 -0
  12. delt/encoders/audio/base.py +129 -0
  13. delt/encoders/audio/clap.py +144 -0
  14. delt/encoders/audio/glap.py +155 -0
  15. delt/encoders/audio/utils.py +36 -0
  16. delt/encoders/base.py +156 -0
  17. delt/encoders/image/__init__.py +6 -0
  18. delt/encoders/image/base.py +129 -0
  19. delt/encoders/image/clip.py +133 -0
  20. delt/encoders/image/siglip.py +133 -0
  21. delt/encoders/text/__init__.py +5 -0
  22. delt/encoders/text/base.py +128 -0
  23. delt/encoders/text/sentence_transformer.py +71 -0
  24. delt/encoders/video/__init__.py +5 -0
  25. delt/encoders/video/base.py +132 -0
  26. delt/encoders/video/utils.py +115 -0
  27. delt/encoders/video/xclip.py +168 -0
  28. delt/examples/hard_distillation/audio.py +51 -0
  29. delt/examples/hard_distillation/image.py +51 -0
  30. delt/examples/hard_distillation/text.py +43 -0
  31. delt/examples/hard_distillation/video.py +77 -0
  32. delt/examples/label_tuning.py +39 -0
  33. delt/examples/pipelines/audio.py +35 -0
  34. delt/examples/pipelines/image.py +33 -0
  35. delt/examples/pipelines/text.py +28 -0
  36. delt/examples/pipelines/video.py +63 -0
  37. delt/examples/soft_distillation/text.py +47 -0
  38. delt/pipelines/__init__.py +15 -0
  39. delt/pipelines/audio.py +31 -0
  40. delt/pipelines/base.py +134 -0
  41. delt/pipelines/image.py +31 -0
  42. delt/pipelines/text.py +31 -0
  43. delt/pipelines/video.py +31 -0
  44. delt/predict.py +69 -0
  45. delt/teachers/__init__.py +13 -0
  46. delt/teachers/audio/__init__.py +5 -0
  47. delt/teachers/audio/lmm.py +94 -0
  48. delt/teachers/base.py +58 -0
  49. delt/teachers/image/__init__.py +5 -0
  50. delt/teachers/image/lmm.py +95 -0
  51. delt/teachers/text/__init__.py +5 -0
  52. delt/teachers/text/llm.py +68 -0
  53. delt/teachers/video/__init__.py +5 -0
  54. delt/teachers/video/lmm.py +107 -0
  55. delt/train.py +236 -0
  56. delt/types.py +34 -0
  57. delt/ui/__init__.py +1 -0
  58. delt/ui/app.py +663 -0
  59. delt/version.py +12 -0
  60. deltpy-1.4.0.dist-info/METADATA +685 -0
  61. deltpy-1.4.0.dist-info/RECORD +64 -0
  62. deltpy-1.4.0.dist-info/WHEEL +5 -0
  63. deltpy-1.4.0.dist-info/licenses/LICENSE +402 -0
  64. deltpy-1.4.0.dist-info/top_level.txt +1 -0
delt/__init__.py ADDED
@@ -0,0 +1,31 @@
1
+ """Main package of delt."""
2
+
3
+ from .encoders import (
4
+ ClapEncoder,
5
+ ClipEncoder,
6
+ SentenceTransformerEncoder,
7
+ SiglipEncoder,
8
+ XclipEncoder,
9
+ get_encoder,
10
+ )
11
+ from .pipelines import (
12
+ AudioPipeline,
13
+ ImagePipeline,
14
+ TextPipeline,
15
+ VideoPipeline,
16
+ )
17
+ from .teachers import LLMTextTeacher
18
+
19
+ __all__ = [
20
+ "SentenceTransformerEncoder",
21
+ "ClipEncoder",
22
+ "SiglipEncoder",
23
+ "ClapEncoder",
24
+ "XclipEncoder",
25
+ "get_encoder",
26
+ "TextPipeline",
27
+ "ImagePipeline",
28
+ "AudioPipeline",
29
+ "VideoPipeline",
30
+ "LLMTextTeacher",
31
+ ]
@@ -0,0 +1,21 @@
1
+ """Subpackage for common utils."""
2
+
3
+ from .experimental import unimodal_kfold
4
+ from .io import base64_encode
5
+ from .iterables import batchify, batchify_tensor, dict_cartesian_product
6
+ from .llm import generate_completion, generate_completions
7
+ from .logging import get_logger
8
+ from .prompting import format_prompt, make_output_model
9
+
10
+ __all__ = [
11
+ "get_logger",
12
+ "format_prompt",
13
+ "unimodal_kfold",
14
+ "dict_cartesian_product",
15
+ "batchify",
16
+ "batchify_tensor",
17
+ "generate_completion",
18
+ "generate_completions",
19
+ "base64_encode",
20
+ "make_output_model",
21
+ ]
@@ -0,0 +1,46 @@
1
+ """Module for experimental utils."""
2
+
3
+ from typing import Any
4
+
5
+ import torch
6
+ from sklearn.model_selection import StratifiedKFold
7
+
8
+
9
+ def unimodal_kfold(
10
+ data: list[Any],
11
+ labels: list[str],
12
+ n_splits: int = 4,
13
+ random_state: int = 42,
14
+ ) -> tuple[list[dict], list[dict]]:
15
+ """
16
+ Create k folds for unimodal settings.
17
+
18
+ Args:
19
+ data (list[Any]): A list of input data samples.
20
+ labels (list[str]): A list of labels corresponding to the input data samples.
21
+ n_splits (int): The number of folds for cross-validation.
22
+ random_state (int): The random seed for reproducibility.
23
+
24
+ Returns:
25
+ tuple[list[dict], list[dict]]: A tuple containing two lists of dictionaries for training and validation splits.
26
+
27
+ """
28
+ kfold = StratifiedKFold(
29
+ n_splits=n_splits, shuffle=True, random_state=random_state
30
+ )
31
+ splits = kfold.split(data, labels)
32
+ train_splits, val_splits = [], []
33
+ for train_idxs, val_idxs in splits:
34
+ if isinstance(data, torch.Tensor):
35
+ train_data = data[train_idxs]
36
+ val_data = data[val_idxs]
37
+ else:
38
+ train_data = [data[i] for i in train_idxs]
39
+ val_data = [data[i] for i in val_idxs]
40
+
41
+ train_labels = [labels[i] for i in train_idxs]
42
+ val_labels = [labels[i] for i in val_idxs]
43
+
44
+ train_splits.append({"x": train_data, "y": train_labels})
45
+ val_splits.append({"x": val_data, "y": val_labels})
46
+ return train_splits, val_splits
delt/common/io.py ADDED
@@ -0,0 +1,24 @@
1
+ """Module for io utils."""
2
+
3
+ from base64 import b64encode
4
+ from typing import Optional
5
+
6
+
7
+ def base64_encode(data: bytes, mime_type: Optional[str] = None) -> str:
8
+ """
9
+ Encode a `bytes` object into base64.
10
+
11
+ If `mime_type` is provided, a Data URI is returned with the mime type in the header.
12
+
13
+ Args:
14
+ data (bytes): bytes to encode.
15
+ mime_type (Optional[str]): mime type of the data.
16
+
17
+ Returns:
18
+ str: a base64 stream in Data URI format if `mime_type` is provided.
19
+
20
+ """
21
+ b64 = b64encode(data).decode("utf-8")
22
+ if mime_type:
23
+ return f"data:{mime_type};base64,{b64}"
24
+ return b64
@@ -0,0 +1,67 @@
1
+ """Module for iterables."""
2
+
3
+ from collections.abc import Iterable, Iterator
4
+ from itertools import islice, product
5
+ from typing import TypeVar
6
+
7
+ import torch
8
+
9
+ T = TypeVar("T")
10
+
11
+
12
+ def batchify(iterable: Iterable[T], batch_size: int) -> Iterator[list[T]]:
13
+ """
14
+ Create batches of `batch_size` from a generic `iterable`.
15
+
16
+ Args:
17
+ iterable (Iterable[T]): generic iterable to get batches from.
18
+ batch_size (int): the batch size.
19
+
20
+ Yields:
21
+ list[T]: a batch of elements as a list.
22
+
23
+ """
24
+ if batch_size <= 0:
25
+ raise ValueError("batch_size must be positive")
26
+
27
+ iterator = iter(iterable)
28
+
29
+ while batch := list(islice(iterator, batch_size)):
30
+ yield batch
31
+
32
+
33
+ def batchify_tensor(
34
+ x: torch.Tensor, batch_size: int
35
+ ) -> Iterator[torch.Tensor]:
36
+ """
37
+ Create batches of `batch_size` from a torch tensor with shape (N, ...).
38
+
39
+ Args:
40
+ x (torch.Tensor): the torch tensor.
41
+ batch_size (int): the batch size.
42
+
43
+ Yields:
44
+ torch.Tensor: a batch of elements as a torch tensor with shape (`batch_size`, ...)
45
+
46
+ """
47
+ if batch_size <= 0:
48
+ raise ValueError("batch_size must be positive")
49
+
50
+ for i in range(0, len(x), batch_size):
51
+ yield x[i : i + batch_size]
52
+
53
+
54
+ def dict_cartesian_product(d: dict) -> list[dict]:
55
+ """
56
+ Get the cartesian product over the fields in a dictionary.
57
+
58
+ Args:
59
+ d (dict): a dictionary.
60
+
61
+ Returns:
62
+ list[dict]: list of dictionaries representing the cartesian product.
63
+
64
+ """
65
+ keys = d.keys()
66
+ values = d.values()
67
+ return [dict(zip(keys, combination)) for combination in product(*values)]
delt/common/llm.py ADDED
@@ -0,0 +1,94 @@
1
+ """Module for litellm utils."""
2
+
3
+ import json
4
+ from concurrent.futures import ThreadPoolExecutor
5
+
6
+ import litellm
7
+ from pydantic import BaseModel
8
+ from tqdm import tqdm
9
+
10
+ from .logging import get_logger
11
+
12
+ _logger = get_logger(__name__)
13
+
14
+
15
+ def generate_completion(
16
+ model_name: str,
17
+ conversation: list[dict],
18
+ response_format: BaseModel,
19
+ decoding_args: dict = {},
20
+ extra_body: dict = {},
21
+ ) -> BaseModel:
22
+ """
23
+ Generate a completion using the specified model for a conversation.
24
+
25
+ Args:
26
+ model_name (str): name of the model.
27
+ conversation (list[dict]): message thread.
28
+ response_format (BaseModel): pydantic model for response formatting.
29
+ decoding_args (dict): additional arguments for decoding.
30
+ extra_body (dict): extra body to send to the model.
31
+
32
+ Returns:
33
+ BaseModel: a completion output.
34
+
35
+ """
36
+ response = litellm.completion(
37
+ model=model_name,
38
+ messages=conversation,
39
+ response_format=response_format,
40
+ extra_body=extra_body,
41
+ **decoding_args,
42
+ )
43
+
44
+ if response.choices[0].finish_reason != "stop":
45
+ raise RuntimeError(
46
+ f"Completion did not finish properly: "
47
+ f"{response.choices[0].finish_reason}"
48
+ )
49
+
50
+ json_response = json.loads(response.choices[0].message.content)
51
+ return response_format(**json_response)
52
+
53
+
54
+ def generate_completions(
55
+ model_name: str,
56
+ conversations: list[list[dict]],
57
+ response_format: BaseModel,
58
+ decoding_args: dict = {},
59
+ batch_size: int = 4,
60
+ extra_body: list[dict] = [],
61
+ ) -> list[BaseModel]:
62
+ """
63
+ Generate completions using the specified model and conversations.
64
+
65
+ Args:
66
+ model_name (str): name of the model.
67
+ conversations (list[list[dict]]): a list of conversations.
68
+ response_format (BaseModel): pydantic model for response formatting.
69
+ decoding_args (dict): additional arguments for decoding.
70
+ batch_size (int): number of concurrent requests.
71
+ extra_body (list[dict]): extra bodies to send to the model.
72
+
73
+ Returns:
74
+ list[BaseModel]: completion outputs.
75
+
76
+ """
77
+ completions, responses = [], []
78
+ with ThreadPoolExecutor(
79
+ max_workers=min(batch_size, len(conversations))
80
+ ) as thread_pool:
81
+ for i, message in enumerate(conversations):
82
+ responses.append(
83
+ thread_pool.submit(
84
+ generate_completion,
85
+ model_name,
86
+ message,
87
+ response_format,
88
+ decoding_args,
89
+ extra_body[i] if extra_body else None,
90
+ )
91
+ )
92
+ completions = [response.result() for response in tqdm(responses)]
93
+
94
+ return completions
delt/common/logging.py ADDED
@@ -0,0 +1,81 @@
1
+ """Module for logging utils."""
2
+
3
+ import logging
4
+ import sys
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+
8
+ _time = datetime.now()
9
+
10
+ COLORS = {
11
+ "grey": "\x1b[38;20m",
12
+ "yellow": "\x1b[33;20m",
13
+ "bold_yellow": "\x1b[33;1m",
14
+ "red": "\x1b[31;20m",
15
+ "bold_red": "\x1b[31;1m",
16
+ "green": "\x1b[32;20m",
17
+ "bold_green": "\x1b[32;1m",
18
+ "blue": "\x1b[34;20m",
19
+ "bold_blue": "\x1b[34;1m",
20
+ "reset": "\x1b[0m",
21
+ }
22
+
23
+
24
+ def color_log(text: str, color: str) -> str:
25
+ """
26
+ Add color to a log text.
27
+
28
+ Args:
29
+ text (str): a text.
30
+ color (str): a color in `COLORS`.
31
+
32
+ Returns:
33
+ str: a text with color codes added.
34
+
35
+ """
36
+ return COLORS[color] + text + COLORS["reset"]
37
+
38
+
39
+ def get_logger(
40
+ module_name: str,
41
+ ) -> logging.Logger:
42
+ """
43
+ Return the logger used across modules.
44
+
45
+ Args:
46
+ module_name (str): name of the module.
47
+
48
+ Returns:
49
+ logging.Logger: the logger.
50
+
51
+ """
52
+ logger = logging.getLogger(module_name)
53
+ logger.setLevel(logging.INFO)
54
+
55
+ formatter = logging.Formatter(
56
+ "[%(asctime)s] - %(levelname)s - %(message)s",
57
+ datefmt="%Y-%m-%d %H:%M:%S",
58
+ )
59
+
60
+ logfile = (
61
+ Path("logs")
62
+ .joinpath(
63
+ _time.strftime("%Y_%m_%d"),
64
+ _time.strftime("%H_%M_%S"),
65
+ "project.log",
66
+ )
67
+ .absolute()
68
+ )
69
+
70
+ logfile.parent.mkdir(parents=True, exist_ok=True)
71
+
72
+ fh = logging.FileHandler(logfile)
73
+ fh.setFormatter(formatter)
74
+
75
+ sh = logging.StreamHandler(sys.stdout)
76
+ sh.setFormatter(formatter)
77
+
78
+ logger.addHandler(sh)
79
+ logger.addHandler(fh)
80
+
81
+ return logger
@@ -0,0 +1,41 @@
1
+ """Module for prompting utils."""
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import BaseModel, create_model
6
+
7
+
8
+ def format_prompt(
9
+ label_verbalizations: dict[str, str], prompt_template: str
10
+ ) -> list[str]:
11
+ """
12
+ Format a prompt template with label verbalizations.
13
+
14
+ Args:
15
+ label_verbalizations (dict[str, str]): verbalizations of the labels, e.g. {"positive": "very cool!", "negative": "horrible"}
16
+ prompt_template (str): template to format label verbalizations, e.g., "This text is {}" being instantiated as "This text is very cool!".
17
+
18
+ Returns:
19
+ list[str]: instantiated prompts, one for each verbalization in `label_verbalizations`.
20
+
21
+ """
22
+ return [
23
+ prompt_template.format(verbalization)
24
+ for verbalization in label_verbalizations.values()
25
+ ]
26
+
27
+
28
+ def make_output_model(label_set: list[str]) -> type[BaseModel]:
29
+ """
30
+ Create a pydantic model dynamically to be used as structured output in classification tasks.
31
+
32
+ Args:
33
+ label_set (list[str]): list of label names.
34
+
35
+ Returns:
36
+ type[BaseModel]: a pydantic model.
37
+
38
+ """
39
+ return create_model(
40
+ "Output", label=(Literal.__getitem__(tuple(label_set)), ...)
41
+ )
delt/common/utils.py ADDED
@@ -0,0 +1 @@
1
+ """Module for common utils."""
@@ -0,0 +1,52 @@
1
+ """Package for encoders."""
2
+
3
+ from typing import Any
4
+
5
+ from .audio import ClapEncoder, GlapEncoder
6
+ from .base import Encoder
7
+ from .image import ClipEncoder, SiglipEncoder
8
+ from .text import SentenceTransformerEncoder
9
+ from .video import XclipEncoder
10
+
11
+ REGISTRY: dict[str, type[Encoder]] = {
12
+ "glap": GlapEncoder,
13
+ "clap": ClapEncoder,
14
+ "clip": ClipEncoder,
15
+ "siglip": SiglipEncoder,
16
+ "sentence-transformer": SentenceTransformerEncoder,
17
+ "xclip": XclipEncoder,
18
+ }
19
+
20
+
21
+ def get_encoder(class_name: str, *args: Any, **kwargs: Any) -> Encoder:
22
+ """
23
+ Instantiate an encoder by class name, given arbitrary arguments.
24
+
25
+ Args:
26
+ class_name (str): class name of the encoder. It should be available in `REGISTRY`.
27
+ *args: positional arguments passed directly to the encoder's constructor.
28
+ **kwargs: keyword arguments passed directly to the encoder's constructor.
29
+
30
+ Returns:
31
+ Encoder: an instantiated encoder.
32
+
33
+ """
34
+ if class_name not in REGISTRY:
35
+ raise KeyError(
36
+ f"Unknown encoder class: {class_name}. Available: {list(REGISTRY.keys())}"
37
+ )
38
+
39
+ encoder_cls = REGISTRY[class_name]
40
+ return encoder_cls(*args, **kwargs)
41
+
42
+
43
+ __all__ = [
44
+ "Encoder",
45
+ "SentenceTransformerEncoder",
46
+ "ClipEncoder",
47
+ "GlapEncoder",
48
+ "SiglipEncoder",
49
+ "ClapEncoder",
50
+ "XclipEncoder",
51
+ "get_encoder",
52
+ ]
@@ -0,0 +1,6 @@
1
+ """Package for audio encoders."""
2
+
3
+ from .clap import ClapEncoder
4
+ from .glap import GlapEncoder
5
+
6
+ __all__ = ["ClapEncoder", "GlapEncoder"]
@@ -0,0 +1,129 @@
1
+ """Module for the base audio encoder."""
2
+
3
+ from abc import abstractmethod
4
+
5
+ import torch
6
+ from transformers import (
7
+ AutoProcessor,
8
+ PreTrainedModel,
9
+ PreTrainedTokenizerBase,
10
+ ProcessorMixin,
11
+ )
12
+
13
+ from delt.encoders.base import Encoder
14
+ from delt.types import Audio, Image, Video
15
+
16
+
17
+ class AudioEncoder(Encoder):
18
+ """Base audio encoder model."""
19
+
20
+ def __init__(
21
+ self,
22
+ encoder_name: str,
23
+ normalize_embeddings: bool = True,
24
+ ) -> None:
25
+ """
26
+ Initialize a encoder model.
27
+
28
+ Args:
29
+ encoder_name (str): name of sentence-transformer-compatible model.
30
+ normalize_embeddings (bool): whether to apply L2 normalization to all embeddings.
31
+
32
+ """
33
+ super().__init__(
34
+ encoder_name=encoder_name,
35
+ normalize_embeddings=normalize_embeddings,
36
+ )
37
+
38
+ @property
39
+ def processor(self) -> PreTrainedTokenizerBase | ProcessorMixin | None:
40
+ """
41
+ Processor tied to an audio encoder.
42
+
43
+ Returns:
44
+ PreTrainedTokenizerBase | ProcessorMixin | None: tokenizers for text
45
+ models, processor mixin for image/audio/video, and `None` for
46
+ models outside the HuggingFace ecosystem.
47
+
48
+ """
49
+ if self._processor is None:
50
+ self._processor = AutoProcessor.from_pretrained(self.encoder_name)
51
+ return self._processor
52
+
53
+ @property
54
+ @abstractmethod
55
+ def encoder(self) -> PreTrainedModel:
56
+ """
57
+ A pretrained audio encoder model.
58
+
59
+ Returns:
60
+ PreTrainedModel: the pretrained audio encoder.
61
+
62
+ """
63
+ ...
64
+
65
+ @abstractmethod
66
+ def get_text_embeddings(
67
+ self, texts: list[str], batch_size: int = 64
68
+ ) -> torch.Tensor:
69
+ """
70
+ Embed a list of texts using the base encoder model.
71
+
72
+ Args:
73
+ texts (list[str]): a list of texts.
74
+ batch_size (int): batch size for inference.
75
+
76
+ Returns:
77
+ torch.Tensor: The text embeddings.
78
+
79
+ """
80
+ ...
81
+
82
+ def get_image_embeddings(
83
+ self, images: list[Image], batch_size: int = 8
84
+ ) -> torch.Tensor:
85
+ """
86
+ Embed a list of images using the base encoder model.
87
+
88
+ Args:
89
+ images (list[Image): list of images.
90
+ batch_size (int): batch size for inference.
91
+
92
+ Returns:
93
+ torch.Tensor: The image embeddings.
94
+
95
+ """
96
+ ...
97
+
98
+ @abstractmethod
99
+ def get_audio_embeddings(
100
+ self, audios: list[Audio], batch_size: int = 16
101
+ ) -> torch.Tensor:
102
+ """
103
+ Embed a list of audios using the base encoder model.
104
+
105
+ Args:
106
+ audios (list[Audio]): list of audios.
107
+ batch_size (int): batch size for inference.
108
+
109
+ Returns:
110
+ torch.Tensor: The audio embeddings.
111
+
112
+ """
113
+ ...
114
+
115
+ def get_video_embeddings(
116
+ self, videos: list[Video], batch_size: int = 16
117
+ ) -> torch.Tensor:
118
+ """
119
+ Embed a list of videos using the base encoder model.
120
+
121
+ Args:
122
+ videos (list[Video]): list of videos.
123
+ batch_size (int): batch size for inference.
124
+
125
+ Returns:
126
+ torch.Tensor: The video embeddings.
127
+
128
+ """
129
+ ...