aspect-data 0.0.1__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.
- aspect/__init__.py +5 -0
- aspect/checkpoint_utils.py +95 -0
- aspect/cli_module/__init__.py +0 -0
- aspect/cli_module/cli.py +129 -0
- aspect/cli_module/featurize.py +187 -0
- aspect/cli_module/io.py +52 -0
- aspect/data.py +577 -0
- aspect/io.py +189 -0
- aspect/package_data.py +35 -0
- aspect/transform/__init__.py +9 -0
- aspect/transform/base.py +182 -0
- aspect/transform/deep_functions.py +143 -0
- aspect/transform/functions.py +333 -0
- aspect/transform/registry.py +17 -0
- aspect/typing.py +31 -0
- aspect_data-0.0.1.dist-info/METADATA +917 -0
- aspect_data-0.0.1.dist-info/RECORD +21 -0
- aspect_data-0.0.1.dist-info/WHEEL +5 -0
- aspect_data-0.0.1.dist-info/entry_points.txt +2 -0
- aspect_data-0.0.1.dist-info/licenses/LICENSE +21 -0
- aspect_data-0.0.1.dist-info/top_level.txt +1 -0
aspect/io.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import TYPE_CHECKING, Any, Iterable, Mapping, Optional, Union
|
|
3
|
+
from functools import partial
|
|
4
|
+
import hashlib
|
|
5
|
+
import os
|
|
6
|
+
import tempfile
|
|
7
|
+
|
|
8
|
+
from carabiner import print_err
|
|
9
|
+
|
|
10
|
+
from numpy.typing import ArrayLike
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from datasets import Dataset, IterableDataset
|
|
14
|
+
from pandas import DataFrame
|
|
15
|
+
else:
|
|
16
|
+
Dataset, DataFrame, IterableDataset = Any, Any, Any
|
|
17
|
+
|
|
18
|
+
from .package_data import DEFAULT_CACHE
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
DATASETS_PREFIX: str = "hf://datasets/"
|
|
22
|
+
|
|
23
|
+
def hasher(s: str, n: int = 16) -> str:
|
|
24
|
+
if isinstance(s, str):
|
|
25
|
+
s = s.encode()
|
|
26
|
+
return hashlib.sha256(s).hexdigest()[:n]
|
|
27
|
+
|
|
28
|
+
def _lock_path(cache_dir: str, key: str) -> str:
|
|
29
|
+
locks_dir = os.path.join(cache_dir, ".locks")
|
|
30
|
+
os.makedirs(locks_dir, exist_ok=True)
|
|
31
|
+
h = hasher(key.encode("utf-8"))
|
|
32
|
+
return os.path.join(locks_dir, f"{h}.lock")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _load_from_file(filename: str, cache: Optional[str] = None) -> Dataset:
|
|
36
|
+
from datasets import load_dataset, Dataset, DatasetDict
|
|
37
|
+
from filelock import FileLock
|
|
38
|
+
|
|
39
|
+
cache = cache or DEFAULT_CACHE
|
|
40
|
+
if filename.removesuffix(".gz").endswith((".csv", ".tsv", ".txt")):
|
|
41
|
+
read_f = partial(
|
|
42
|
+
load_dataset,
|
|
43
|
+
path="csv",
|
|
44
|
+
data_files=filename,
|
|
45
|
+
cache_dir=cache,
|
|
46
|
+
sep="," if filename.endswith((".csv", ".csv.gz")) else "\t",
|
|
47
|
+
)
|
|
48
|
+
lock_key = ("csv", read_f.keywords.get('sep'))
|
|
49
|
+
elif filename.endswith((".arrow", ".hd5", ".json", ".parquet", ".xml")):
|
|
50
|
+
_, ext = os.path.splitext(filename)
|
|
51
|
+
protocol = ext.lstrip(".")
|
|
52
|
+
read_f = partial(
|
|
53
|
+
load_dataset,
|
|
54
|
+
path=protocol,
|
|
55
|
+
data_files=filename,
|
|
56
|
+
cache_dir=cache,
|
|
57
|
+
)
|
|
58
|
+
lock_key = (protocol, "")
|
|
59
|
+
elif filename.endswith(".hf"):
|
|
60
|
+
read_f = partial(
|
|
61
|
+
Dataset.load_from_disk,
|
|
62
|
+
dataset_path=filename,
|
|
63
|
+
)
|
|
64
|
+
lock_key = ("hf", "")
|
|
65
|
+
else:
|
|
66
|
+
raise IOError(f"Could not infer how to open '{filename}' from its extension.")
|
|
67
|
+
|
|
68
|
+
# If no cache, nothing sensible to lock on
|
|
69
|
+
if cache is None:
|
|
70
|
+
ds = read_f()
|
|
71
|
+
|
|
72
|
+
# Cross-task lock on the shared filesystem
|
|
73
|
+
lockfile = _lock_path(cache, "_".join(lock_key))
|
|
74
|
+
with FileLock(lockfile, timeout=60. * 60.):
|
|
75
|
+
ds = read_f()
|
|
76
|
+
if isinstance(ds, DatasetDict):
|
|
77
|
+
return ds["train"]
|
|
78
|
+
else:
|
|
79
|
+
return ds
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _load_from_dataframe(
|
|
83
|
+
dataframe: Union[DataFrame, Mapping[str, ArrayLike], Iterable[Mapping[str, ArrayLike]]],
|
|
84
|
+
cache: Optional[str] = None
|
|
85
|
+
) -> Dataset:
|
|
86
|
+
from pandas import DataFrame
|
|
87
|
+
|
|
88
|
+
if cache is None:
|
|
89
|
+
cache = DEFAULT_CACHE
|
|
90
|
+
print_err(f"Defaulting to cache: {cache}")
|
|
91
|
+
if not isinstance(dataframe, DataFrame):
|
|
92
|
+
dataframe = DataFrame(dataframe)
|
|
93
|
+
|
|
94
|
+
hash_name = hasher(dataframe.to_string())
|
|
95
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
96
|
+
csv_filename = os.path.join(tmpdir, f"{hash_name}.csv.gz")
|
|
97
|
+
dataframe.to_csv(csv_filename, index=False)
|
|
98
|
+
ds = _load_from_file(
|
|
99
|
+
csv_filename,
|
|
100
|
+
cache=cache,
|
|
101
|
+
)
|
|
102
|
+
return ds
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _get_ref_chunk(
|
|
106
|
+
s,
|
|
107
|
+
sep: Optional[str] = None,
|
|
108
|
+
all_seps: str = "@~:"
|
|
109
|
+
) -> str:
|
|
110
|
+
if sep is not None:
|
|
111
|
+
if sep in s:
|
|
112
|
+
s = s.rpartition(sep)[-1]
|
|
113
|
+
else:
|
|
114
|
+
return None
|
|
115
|
+
for _sep in all_seps:
|
|
116
|
+
s = s.partition(_sep)[0]
|
|
117
|
+
return s
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _resolve_hf_hub_dataset(
|
|
121
|
+
ref: str,
|
|
122
|
+
cache: Optional[str] = None
|
|
123
|
+
) -> Dataset:
|
|
124
|
+
from datasets import concatenate_datasets, load_dataset, DatasetDict
|
|
125
|
+
|
|
126
|
+
ref = ref.removeprefix(DATASETS_PREFIX).removeprefix("hf://")
|
|
127
|
+
seps = "@~:"
|
|
128
|
+
ver = _get_ref_chunk(ref, "@", all_seps=seps)
|
|
129
|
+
split = _get_ref_chunk(ref, ":", all_seps=seps)
|
|
130
|
+
config = _get_ref_chunk(ref, "~", all_seps=seps)
|
|
131
|
+
|
|
132
|
+
ds = load_dataset(
|
|
133
|
+
path=_get_ref_chunk(ref, all_seps=seps),
|
|
134
|
+
name=config,
|
|
135
|
+
split=split,
|
|
136
|
+
revision=ver,
|
|
137
|
+
cache_dir=cache,
|
|
138
|
+
)
|
|
139
|
+
if isinstance(ds, DatasetDict):
|
|
140
|
+
ds = concatenate_datasets([v for key, v in ds.items()])
|
|
141
|
+
return ds
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class AutoDataset:
|
|
145
|
+
|
|
146
|
+
def __init__(self, dataset):
|
|
147
|
+
self._dataset = dataset
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def load(
|
|
151
|
+
cls,
|
|
152
|
+
data: Union[str, DataFrame],
|
|
153
|
+
cache: Optional[str] = None
|
|
154
|
+
) -> Union[Dataset, IterableDataset]:
|
|
155
|
+
from datasets import load_dataset, Dataset, IterableDataset
|
|
156
|
+
from pandas import DataFrame
|
|
157
|
+
|
|
158
|
+
if isinstance(data, (Dataset, IterableDataset)):
|
|
159
|
+
dataset = data
|
|
160
|
+
elif isinstance(data, (DataFrame, Mapping)):
|
|
161
|
+
dataset = _load_from_dataframe(
|
|
162
|
+
data,
|
|
163
|
+
cache=cache,
|
|
164
|
+
)
|
|
165
|
+
elif isinstance(data, str):
|
|
166
|
+
if data.startswith("hf://"):
|
|
167
|
+
dataset = _resolve_hf_hub_dataset(
|
|
168
|
+
data,
|
|
169
|
+
cache=cache,
|
|
170
|
+
)
|
|
171
|
+
elif os.path.exists(data):
|
|
172
|
+
dataset = _load_from_file(
|
|
173
|
+
data,
|
|
174
|
+
cache=cache,
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
raise ValueError(
|
|
178
|
+
f"""
|
|
179
|
+
If `data` is a string, it must start with "{DATASETS_PREFIX}" or a path to an existing file.
|
|
180
|
+
It was "{data}".
|
|
181
|
+
"""
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
raise ValueError(
|
|
185
|
+
"""
|
|
186
|
+
Data must be a string, Dataset, dictionary, or Pandas DataFrame.
|
|
187
|
+
"""
|
|
188
|
+
)
|
|
189
|
+
return cls(dataset)
|
aspect/package_data.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Tools for loading and writing reusable package data."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from platformdirs import user_cache_dir
|
|
6
|
+
|
|
7
|
+
from . import app_name, __author__, __version__
|
|
8
|
+
|
|
9
|
+
CACHE_DIR = user_cache_dir(
|
|
10
|
+
app_name,
|
|
11
|
+
appauthor=False,
|
|
12
|
+
version=__version__,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
ELUENT_CACHE = "ASPECT_CACHE"
|
|
16
|
+
DEFAULT_CACHE = CACHE_DIR
|
|
17
|
+
|
|
18
|
+
def _get_data_path(
|
|
19
|
+
filename: str,
|
|
20
|
+
env_key: str = ELUENT_CACHE,
|
|
21
|
+
default: str = DEFAULT_CACHE
|
|
22
|
+
) -> str:
|
|
23
|
+
"""Returns the path to a writable version of a package data file.
|
|
24
|
+
|
|
25
|
+
Copies it from the package resources if not present.
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
cache_dir = os.environ.get(
|
|
29
|
+
env_key,
|
|
30
|
+
os.path.expanduser(default),
|
|
31
|
+
)
|
|
32
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
33
|
+
os.environ["HF_HOME"] = cache_dir
|
|
34
|
+
os.environ["HF_DATASETS_CACHE"] = cache_dir
|
|
35
|
+
return cache_dir, os.path.join(cache_dir, filename)
|
aspect/transform/base.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Classes to enable JSON serialization of preprocessing functions."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, Iterable, Mapping, Union
|
|
4
|
+
from dataclasses import asdict, dataclass, field
|
|
5
|
+
from functools import partial
|
|
6
|
+
|
|
7
|
+
from carabiner import print_err
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from .. import app_name, __version__
|
|
11
|
+
from ..checkpoint_utils import save_json, _load_json
|
|
12
|
+
from .registry import FUNCTION_REGISTRY
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ColumnTransform:
|
|
17
|
+
|
|
18
|
+
"""Serializable wrapper around a registered transform function.
|
|
19
|
+
|
|
20
|
+
Examples
|
|
21
|
+
========
|
|
22
|
+
Basic construction and output column naming:
|
|
23
|
+
|
|
24
|
+
>>> ct = ColumnTransform(name="log", input_column="affinity")
|
|
25
|
+
>>> ct.input_column
|
|
26
|
+
'affinity'
|
|
27
|
+
>>> ct.name
|
|
28
|
+
'log'
|
|
29
|
+
>>> ct.output_column.startswith("aspect-data/")
|
|
30
|
+
True
|
|
31
|
+
|
|
32
|
+
Output column is deterministic:
|
|
33
|
+
|
|
34
|
+
>>> ct2 = ColumnTransform(name="log", input_column="affinity")
|
|
35
|
+
>>> ct.output_column == ct2.output_column
|
|
36
|
+
True
|
|
37
|
+
|
|
38
|
+
Output column changes with input_column:
|
|
39
|
+
|
|
40
|
+
>>> ct3 = ColumnTransform(name="log", input_column="other")
|
|
41
|
+
>>> ct.output_column != ct3.output_column
|
|
42
|
+
True
|
|
43
|
+
|
|
44
|
+
Output column changes with kwargs:
|
|
45
|
+
|
|
46
|
+
>>> ct4 = ColumnTransform(name="hash", input_column="assay", kwargs={"ndim": 64})
|
|
47
|
+
>>> ct5 = ColumnTransform(name="hash", input_column="assay", kwargs={"ndim": 128})
|
|
48
|
+
>>> ct4.output_column != ct5.output_column
|
|
49
|
+
True
|
|
50
|
+
|
|
51
|
+
Applying a transform:
|
|
52
|
+
|
|
53
|
+
>>> import numpy as np
|
|
54
|
+
>>> data = {"affinity": [1.0, 10.0, 100.0]}
|
|
55
|
+
>>> result = ct(data)
|
|
56
|
+
>>> ct.output_column in result
|
|
57
|
+
True
|
|
58
|
+
>>> np.allclose(result[ct.output_column], np.log([1., 10., 100.])[:,None])
|
|
59
|
+
True
|
|
60
|
+
|
|
61
|
+
Idempotency — calling twice skips recomputation:
|
|
62
|
+
|
|
63
|
+
>>> result2 = ct(result)
|
|
64
|
+
>>> np.array_equal(result[ct.output_column], result2[ct.output_column])
|
|
65
|
+
True
|
|
66
|
+
|
|
67
|
+
Serialization roundtrip:
|
|
68
|
+
|
|
69
|
+
>>> d = ct.to_dict()
|
|
70
|
+
>>> ct_restored = ColumnTransform.from_dict(d)
|
|
71
|
+
>>> ct_restored.output_column == ct.output_column
|
|
72
|
+
True
|
|
73
|
+
>>> data2 = {"affinity": [1.0, 10.0, 100.0]}
|
|
74
|
+
>>> result3 = ct_restored(data2)
|
|
75
|
+
>>> np.allclose(result3[ct.output_column], np.log([1., 10., 100.])[:,None])
|
|
76
|
+
True
|
|
77
|
+
|
|
78
|
+
Chained application propagates columns correctly:
|
|
79
|
+
|
|
80
|
+
>>> ct_hash = ColumnTransform(name="hash", input_column="assay", kwargs={"ndim": 16})
|
|
81
|
+
>>> data3 = {"assay": ["MIC", "MIC", "binding"]}
|
|
82
|
+
>>> r1 = ct_hash(data3)
|
|
83
|
+
>>> ct_hash.output_column in r1
|
|
84
|
+
True
|
|
85
|
+
>>> r1[ct_hash.output_column].shape
|
|
86
|
+
(3, 16)
|
|
87
|
+
|
|
88
|
+
Unregistered name raises:
|
|
89
|
+
|
|
90
|
+
>>> ColumnTransform(name="nonexistent", input_column="x")
|
|
91
|
+
Traceback (most recent call last):
|
|
92
|
+
...
|
|
93
|
+
ValueError: Function 'nonexistent' is not registered...
|
|
94
|
+
|
|
95
|
+
show() lists registered functions:
|
|
96
|
+
|
|
97
|
+
>>> transforms = ColumnTransform.show()
|
|
98
|
+
>>> "log" in transforms
|
|
99
|
+
True
|
|
100
|
+
>>> "hash" in transforms
|
|
101
|
+
True
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
name: Union[str, Callable]
|
|
106
|
+
input_column: str
|
|
107
|
+
kwargs: Mapping[str, Any] = field(default_factory = dict)
|
|
108
|
+
_version: str = field(default=__version__)
|
|
109
|
+
_app: str = field(default=app_name)
|
|
110
|
+
|
|
111
|
+
def __post_init__(self):
|
|
112
|
+
if isinstance(self.name, str):
|
|
113
|
+
try:
|
|
114
|
+
self.closure = FUNCTION_REGISTRY[self.name]
|
|
115
|
+
except KeyError:
|
|
116
|
+
raise ValueError(
|
|
117
|
+
f"Function '{self.name}' is not registered. Try: {', '.join(FUNCTION_REGISTRY)}"
|
|
118
|
+
)
|
|
119
|
+
self._serializable = True
|
|
120
|
+
elif isinstance(self.name, Callable):
|
|
121
|
+
self.closure = self.name
|
|
122
|
+
self.name = str(self.name)
|
|
123
|
+
self._serializable = False
|
|
124
|
+
else:
|
|
125
|
+
raise ValueError(f"Transformation function must be a str or callable")
|
|
126
|
+
self.hash = self._get_hash()
|
|
127
|
+
self.hash_stub = self.hash[:8]
|
|
128
|
+
self.output_column = self._get_output_column()
|
|
129
|
+
self.function = self.closure(**self.kwargs)
|
|
130
|
+
|
|
131
|
+
def _get_hash(self):
|
|
132
|
+
from datasets.fingerprint import Hasher
|
|
133
|
+
return Hasher.hash(self.to_dict())
|
|
134
|
+
|
|
135
|
+
def _get_output_column(self):
|
|
136
|
+
return f"{self._app}/{self._version}/{self.name}:{self.input_column}/{self.hash_stub}"
|
|
137
|
+
|
|
138
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
139
|
+
"""Convert the function call to a JSON-serializable format."""
|
|
140
|
+
if self._serializable:
|
|
141
|
+
return asdict(self)
|
|
142
|
+
else:
|
|
143
|
+
raise AttributeError(f"Cannot serialize {self} with callable {self.name}.")
|
|
144
|
+
|
|
145
|
+
def to_file(self, filename: str) -> None:
|
|
146
|
+
"""Save to JSON."""
|
|
147
|
+
return save_json(self.to_dict(), filename)
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def from_dict(cls, data: Mapping[str, Union[str, Mapping]]) -> 'ColumnTransform':
|
|
151
|
+
"""Reconstruct the function call from JSON."""
|
|
152
|
+
return cls(**data)
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def from_file(cls, filename: str) -> 'ColumnTransform':
|
|
156
|
+
"""Load from JSON."""
|
|
157
|
+
return cls.from_dict(_load_json(filename))
|
|
158
|
+
|
|
159
|
+
@classmethod
|
|
160
|
+
def show(cls) -> 'ColumnTransform':
|
|
161
|
+
"""List registered functions."""
|
|
162
|
+
return tuple(FUNCTION_REGISTRY)
|
|
163
|
+
|
|
164
|
+
def apply(
|
|
165
|
+
self,
|
|
166
|
+
inputs: Mapping[str, Iterable]
|
|
167
|
+
) -> np.ndarray:
|
|
168
|
+
result = self.function(inputs, self.input_column)
|
|
169
|
+
if result.ndim == 1:
|
|
170
|
+
result = result[:, None]
|
|
171
|
+
return result
|
|
172
|
+
|
|
173
|
+
def __call__(
|
|
174
|
+
self,
|
|
175
|
+
inputs: Mapping[str, Iterable]
|
|
176
|
+
) -> Dict[str, np.ndarray]:
|
|
177
|
+
"""Execute the function with stored parameters."""
|
|
178
|
+
if self.output_column not in inputs:
|
|
179
|
+
inputs[self.output_column] = self.apply(inputs)
|
|
180
|
+
else:
|
|
181
|
+
print_err(f"[WARN] {self.output_column} already present, skipping.")
|
|
182
|
+
return inputs
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Preprocessing functions from deep embeddings."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Mapping, Tuple
|
|
4
|
+
from functools import partial
|
|
5
|
+
|
|
6
|
+
from carabiner import cast, print_err
|
|
7
|
+
import numpy as np
|
|
8
|
+
from numpy.typing import ArrayLike
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from transformers import PreTrainedTokenizerBase, PreTrainedModel
|
|
12
|
+
else:
|
|
13
|
+
PreTrainedTokenizerBase, PreTrainedModel = Any, Any
|
|
14
|
+
|
|
15
|
+
from .registry import register_function
|
|
16
|
+
from ..typing import StrOrIterableOfStr
|
|
17
|
+
|
|
18
|
+
def get_device():
|
|
19
|
+
import torch
|
|
20
|
+
return torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _index_into(x: ArrayLike, i: int):
|
|
24
|
+
return x[:,i,:]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _get_value_from_tuple(f: Callable, **kwargs):
|
|
28
|
+
partial_f = partial(f, **kwargs)
|
|
29
|
+
|
|
30
|
+
def _f(*args, **fkwargs):
|
|
31
|
+
return partial_f(*args, **fkwargs).values
|
|
32
|
+
|
|
33
|
+
return _f
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _resolve_bart_model(ref: str) -> Tuple[PreTrainedTokenizerBase, PreTrainedModel]:
|
|
37
|
+
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
|
|
38
|
+
import torch
|
|
39
|
+
model = AutoModelForSeq2SeqLM.from_pretrained(ref)
|
|
40
|
+
return AutoTokenizer.from_pretrained(ref), torch.compile(model, fullgraph=True).to(get_device())
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _tokenize_for_embedding(
|
|
44
|
+
x: Iterable[str],
|
|
45
|
+
tokenizer: PreTrainedTokenizerBase
|
|
46
|
+
) -> Dict[str, np.ndarray]:
|
|
47
|
+
|
|
48
|
+
inputs = [
|
|
49
|
+
_x if _x is not None
|
|
50
|
+
else '<unk>'
|
|
51
|
+
for _x in cast(x, to=list)
|
|
52
|
+
]
|
|
53
|
+
tokenizer_args = {
|
|
54
|
+
"return_tensors": "pt",
|
|
55
|
+
"padding": True,
|
|
56
|
+
}
|
|
57
|
+
try:
|
|
58
|
+
tokenized = tokenizer(inputs, **tokenizer_args)
|
|
59
|
+
except (ValueError, TypeError) as e:
|
|
60
|
+
print_err(e)
|
|
61
|
+
print_err(f"Tokenizing this batch failed: {inputs}")
|
|
62
|
+
tokenized = tokenizer(
|
|
63
|
+
['<unk>'] * len(inputs),
|
|
64
|
+
**tokenizer_args,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
key: tokenized[key].to(get_device())
|
|
69
|
+
for key in ['input_ids', 'attention_mask']
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@register_function("hf-bart")
|
|
74
|
+
def HfBART(
|
|
75
|
+
ref: str,
|
|
76
|
+
aggregator: StrOrIterableOfStr = "mean"
|
|
77
|
+
) -> np.ndarray:
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
from transformers import PreTrainedTokenizerBase, PreTrainedModel
|
|
81
|
+
except ImportError:
|
|
82
|
+
raise ImportError("Hugging Face transformers not installed. Try `pip install aspect[deep]`.")
|
|
83
|
+
else:
|
|
84
|
+
import torch
|
|
85
|
+
|
|
86
|
+
EMBEDDING_AGGREGATORS = {
|
|
87
|
+
"start": partial(_index_into, i=0),
|
|
88
|
+
"end": partial(_index_into, i=-1),
|
|
89
|
+
"sum": partial(torch.sum, dim=-2),
|
|
90
|
+
"mean": partial(torch.mean, dim=-2),
|
|
91
|
+
"median": _get_value_from_tuple(torch.median, dim=-2),
|
|
92
|
+
"max": _get_value_from_tuple(torch.max, dim=-2),
|
|
93
|
+
}
|
|
94
|
+
DEFAULT_AGGREGATOR = EMBEDDING_AGGREGATORS["mean"]
|
|
95
|
+
|
|
96
|
+
def _aggregated_embedding(
|
|
97
|
+
x: torch.Tensor,
|
|
98
|
+
aggregators: Iterable[Callable] = (DEFAULT_AGGREGATOR, )
|
|
99
|
+
):
|
|
100
|
+
aggregated = [
|
|
101
|
+
a(x).detach().cpu().numpy() for a in aggregators
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
return np.concatenate(aggregated, axis=-1)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
aggregators = [a.casefold() for a in cast(aggregator, to=list)]
|
|
108
|
+
incorrect_agg = [
|
|
109
|
+
a for a in aggregators if a not in EMBEDDING_AGGREGATORS
|
|
110
|
+
]
|
|
111
|
+
if len(incorrect_agg) > 0:
|
|
112
|
+
raise ValueError(f"Requested aggregators do not exist: {incorrect_agg}")
|
|
113
|
+
else:
|
|
114
|
+
aggregators = [EMBEDDING_AGGREGATORS[a] for a in aggregators]
|
|
115
|
+
tokenizer, model = _resolve_bart_model(ref)
|
|
116
|
+
|
|
117
|
+
def _hf_bart(
|
|
118
|
+
data: Mapping[str, Iterable],
|
|
119
|
+
input_column: str
|
|
120
|
+
) -> np.ndarray:
|
|
121
|
+
tokenized_inputs = _tokenize_for_embedding(
|
|
122
|
+
data[input_column],
|
|
123
|
+
tokenizer=tokenizer,
|
|
124
|
+
)
|
|
125
|
+
model.eval()
|
|
126
|
+
with torch.no_grad():
|
|
127
|
+
outputs = model(
|
|
128
|
+
**tokenized_inputs,
|
|
129
|
+
decoder_input_ids=tokenized_inputs['input_ids'],
|
|
130
|
+
output_hidden_states=True,
|
|
131
|
+
return_dict=True,
|
|
132
|
+
)
|
|
133
|
+
enc_last = _aggregated_embedding(
|
|
134
|
+
outputs.encoder_hidden_states[-1],
|
|
135
|
+
aggregators=aggregators,
|
|
136
|
+
)
|
|
137
|
+
dec_last = _aggregated_embedding(
|
|
138
|
+
outputs.decoder_hidden_states[-1],
|
|
139
|
+
aggregators=aggregators,
|
|
140
|
+
)
|
|
141
|
+
return np.concatenate([enc_last, dec_last], axis=-1)
|
|
142
|
+
|
|
143
|
+
return _hf_bart
|