mostlyai-engine 1.0.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.
- mostlyai/engine/__init__.py +27 -0
- mostlyai/engine/_common.py +612 -0
- mostlyai/engine/_dtypes.py +58 -0
- mostlyai/engine/_encoding_types/__init__.py +13 -0
- mostlyai/engine/_encoding_types/language/__init__.py +13 -0
- mostlyai/engine/_encoding_types/language/text.py +41 -0
- mostlyai/engine/_encoding_types/tabular/__init__.py +13 -0
- mostlyai/engine/_encoding_types/tabular/categorical.py +115 -0
- mostlyai/engine/_encoding_types/tabular/character.py +155 -0
- mostlyai/engine/_encoding_types/tabular/datetime.py +254 -0
- mostlyai/engine/_encoding_types/tabular/itt.py +352 -0
- mostlyai/engine/_encoding_types/tabular/lat_long.py +353 -0
- mostlyai/engine/_encoding_types/tabular/numeric.py +548 -0
- mostlyai/engine/_language/__init__.py +17 -0
- mostlyai/engine/_language/common.py +110 -0
- mostlyai/engine/_language/encoding.py +162 -0
- mostlyai/engine/_language/engine/__init__.py +13 -0
- mostlyai/engine/_language/engine/base.py +51 -0
- mostlyai/engine/_language/engine/hf_engine.py +136 -0
- mostlyai/engine/_language/engine/vllm_engine.py +187 -0
- mostlyai/engine/_language/formatron_utils.py +114 -0
- mostlyai/engine/_language/generation.py +309 -0
- mostlyai/engine/_language/lstm.py +139 -0
- mostlyai/engine/_language/tokenizer_utils.py +144 -0
- mostlyai/engine/_language/training.py +718 -0
- mostlyai/engine/_memory.py +69 -0
- mostlyai/engine/_tabular/__init__.py +13 -0
- mostlyai/engine/_tabular/argn.py +1382 -0
- mostlyai/engine/_tabular/common.py +29 -0
- mostlyai/engine/_tabular/encoding.py +406 -0
- mostlyai/engine/_tabular/fairness.py +245 -0
- mostlyai/engine/_tabular/generation.py +1157 -0
- mostlyai/engine/_tabular/training.py +856 -0
- mostlyai/engine/_training_utils.py +159 -0
- mostlyai/engine/_workspace.py +225 -0
- mostlyai/engine/analysis.py +571 -0
- mostlyai/engine/domain.py +171 -0
- mostlyai/engine/encoding.py +42 -0
- mostlyai/engine/generation.py +110 -0
- mostlyai/engine/splitting.py +257 -0
- mostlyai/engine/training.py +104 -0
- mostlyai_engine-1.0.0.dist-info/METADATA +179 -0
- mostlyai_engine-1.0.0.dist-info/RECORD +45 -0
- mostlyai_engine-1.0.0.dist-info/WHEEL +4 -0
- mostlyai_engine-1.0.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,1382 @@
|
|
|
1
|
+
# Copyright 2025 MOSTLY AI
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
TabularARGN
|
|
17
|
+
|
|
18
|
+
This module provides two models for tabular data:
|
|
19
|
+
|
|
20
|
+
- FlatModel: A model that learns to generate tabular data, one row at a time.
|
|
21
|
+
- SequentialModel: A model that learns to generate tabular data, one coherent sequence at a time.
|
|
22
|
+
|
|
23
|
+
Each model can learn to generate data given some context data, which can either consist of scalar or sequential data.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from enum import Enum
|
|
27
|
+
from functools import partial
|
|
28
|
+
import logging
|
|
29
|
+
from typing import Any, Literal
|
|
30
|
+
|
|
31
|
+
import numpy as np
|
|
32
|
+
import torch
|
|
33
|
+
from torch import nn
|
|
34
|
+
|
|
35
|
+
from opacus.layers import DPLSTM
|
|
36
|
+
|
|
37
|
+
from mostlyai.engine._common import (
|
|
38
|
+
CTXFLT,
|
|
39
|
+
CTXSEQ,
|
|
40
|
+
get_columns_from_cardinalities,
|
|
41
|
+
get_sub_columns_from_cardinalities,
|
|
42
|
+
get_sub_columns_lookup,
|
|
43
|
+
get_sub_columns_nested_from_cardinalities,
|
|
44
|
+
)
|
|
45
|
+
from mostlyai.engine._tabular.fairness import apply_fairness_transforms
|
|
46
|
+
|
|
47
|
+
_LOG = logging.getLogger(__name__)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ModelSize(str, Enum):
|
|
51
|
+
S = "S"
|
|
52
|
+
M = "M"
|
|
53
|
+
L = "L"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
ModelSizeOrUnits = ModelSize | dict[str, list[int]]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_no_of_model_parameters(model) -> int:
|
|
60
|
+
trainable_count = sum([np.prod(p.shape) for p in model.parameters()])
|
|
61
|
+
return trainable_count
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_model_units(model: nn.Module) -> dict[str, Any]:
|
|
65
|
+
embedder_units = {
|
|
66
|
+
name[name.find("embedder@") :]: module.embedding_dim
|
|
67
|
+
for name, module in model.named_modules()
|
|
68
|
+
if ".embedder@" in name
|
|
69
|
+
}
|
|
70
|
+
column_embedder_units = {
|
|
71
|
+
name[name.find("column_embedder@") :]: module.out_features
|
|
72
|
+
for name, module in model.named_modules()
|
|
73
|
+
if ".column_embedder@" in name and not isinstance(module, nn.Identity) # columns are compressed optionally
|
|
74
|
+
}
|
|
75
|
+
flat_context_units = {
|
|
76
|
+
name[name.find("flat_context@") :]: [layer.out_features for layer in module]
|
|
77
|
+
for name, module in model.named_modules()
|
|
78
|
+
if ".flat_context@" in name
|
|
79
|
+
if isinstance(module, nn.ModuleList)
|
|
80
|
+
}
|
|
81
|
+
sequential_context_units = {
|
|
82
|
+
name[name.find("sequential_context@") :]: [module.hidden_size] * module.num_layers
|
|
83
|
+
for name, module in model.named_modules()
|
|
84
|
+
if ".sequential_context@" in name
|
|
85
|
+
if isinstance(module, nn.LSTM) or isinstance(module, DPLSTM)
|
|
86
|
+
}
|
|
87
|
+
history_units = {
|
|
88
|
+
name[name.find("history@") :]: [module.hidden_size] * module.num_layers
|
|
89
|
+
for name, module in model.named_modules()
|
|
90
|
+
if ".history@" in name
|
|
91
|
+
if isinstance(module, nn.LSTM) or isinstance(module, DPLSTM)
|
|
92
|
+
}
|
|
93
|
+
regressor_units = {
|
|
94
|
+
name[name.find("regressor@") :]: [layer.out_features for layer in module]
|
|
95
|
+
for name, module in model.named_modules()
|
|
96
|
+
if ".regressor@" in name
|
|
97
|
+
if isinstance(module, nn.ModuleList)
|
|
98
|
+
}
|
|
99
|
+
heuristics_dict = (
|
|
100
|
+
embedder_units
|
|
101
|
+
| column_embedder_units
|
|
102
|
+
| flat_context_units
|
|
103
|
+
| sequential_context_units
|
|
104
|
+
| history_units
|
|
105
|
+
| regressor_units
|
|
106
|
+
)
|
|
107
|
+
return heuristics_dict
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
####################
|
|
111
|
+
### HEURISTICS ###
|
|
112
|
+
####################
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _embedding_heuristic(id: str, model_size: ModelSizeOrUnits, dim_input: int) -> int:
|
|
116
|
+
if isinstance(model_size, dict):
|
|
117
|
+
return model_size[id]
|
|
118
|
+
model_size_output_dim = dict(
|
|
119
|
+
S=max(10, int(2 * np.ceil(dim_input**0.15))),
|
|
120
|
+
M=max(10, int(3 * np.ceil(dim_input**0.25))),
|
|
121
|
+
L=max(10, int(4 * np.ceil(dim_input**0.33))),
|
|
122
|
+
)
|
|
123
|
+
return min(dim_input, model_size_output_dim[model_size])
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _column_embedding_heuristic(
|
|
127
|
+
id: str,
|
|
128
|
+
model_size: ModelSizeOrUnits,
|
|
129
|
+
dim_input: int,
|
|
130
|
+
n_sub_cols: int,
|
|
131
|
+
compress_enabled: bool,
|
|
132
|
+
) -> int:
|
|
133
|
+
if isinstance(model_size, dict):
|
|
134
|
+
return model_size.get(id, dim_input)
|
|
135
|
+
model_size_output_dim = dict(
|
|
136
|
+
S=int(4 + n_sub_cols),
|
|
137
|
+
M=int(10 + n_sub_cols),
|
|
138
|
+
L=int(16 + n_sub_cols),
|
|
139
|
+
)
|
|
140
|
+
compress = compress_enabled and n_sub_cols > 2
|
|
141
|
+
dim_output = model_size_output_dim[model_size] if compress else dim_input
|
|
142
|
+
# dim_output should always be at most dim_input
|
|
143
|
+
return min(dim_input, dim_output)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _regressor_heuristic(id: str, model_size: ModelSizeOrUnits, dim_input: int, cardinality: int) -> list[int]:
|
|
147
|
+
if isinstance(model_size, dict):
|
|
148
|
+
return model_size[id]
|
|
149
|
+
model_size_layers = dict(S=[4], M=[16], L=[16, 16])
|
|
150
|
+
dims = [dim_input]
|
|
151
|
+
layers = model_size_layers[model_size]
|
|
152
|
+
# first layers depend on input dimension
|
|
153
|
+
for idx, unit in enumerate(layers[:-1]):
|
|
154
|
+
coefficient = round(np.log(max(dims[idx], np.e)))
|
|
155
|
+
dims.append(unit * coefficient)
|
|
156
|
+
# last layer depends on cardinality
|
|
157
|
+
unit = layers[-1]
|
|
158
|
+
coefficient = round(np.log(max(cardinality, np.e)))
|
|
159
|
+
dims.append(unit * coefficient)
|
|
160
|
+
return dims[1:]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _flat_context_heuristic(id: str, model_size: ModelSizeOrUnits, dim_input: int) -> list[int]:
|
|
164
|
+
if isinstance(model_size, dict):
|
|
165
|
+
return model_size[id]
|
|
166
|
+
model_size_layers = dict(S=[8], M=[64], L=[128])
|
|
167
|
+
layers = model_size_layers[model_size]
|
|
168
|
+
coefficient = round(np.log(max(dim_input, np.e)))
|
|
169
|
+
dims = [unit * coefficient for unit in layers]
|
|
170
|
+
return dims
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _sequential_context_heuristic(
|
|
174
|
+
id: str, model_size: ModelSizeOrUnits, dim_input: int, seq_len_median: int
|
|
175
|
+
) -> list[int]:
|
|
176
|
+
if isinstance(model_size, dict):
|
|
177
|
+
return model_size[id]
|
|
178
|
+
model_size_layers = dict(S=[8], M=[32], L=[64, 64])
|
|
179
|
+
layers = model_size_layers[model_size]
|
|
180
|
+
coefficient = round(np.log(max(dim_input * seq_len_median, np.e)))
|
|
181
|
+
dims = [unit * coefficient for unit in layers]
|
|
182
|
+
return dims
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _history_heuristic(id: str, model_size: ModelSizeOrUnits, dim_input: int, seq_len_median: int) -> list[int]:
|
|
186
|
+
if isinstance(model_size, dict):
|
|
187
|
+
return model_size[id]
|
|
188
|
+
model_size_layers = dict(S=[16], M=[64], L=[128, 128])
|
|
189
|
+
layers = model_size_layers[model_size]
|
|
190
|
+
coefficient = round(np.log(max(dim_input * seq_len_median, np.e)))
|
|
191
|
+
dims = [unit * coefficient for unit in layers]
|
|
192
|
+
return dims
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
#######################
|
|
196
|
+
### BUILDING BLOCKS ###
|
|
197
|
+
#######################
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class Embedders(nn.Module):
|
|
201
|
+
def __init__(
|
|
202
|
+
self,
|
|
203
|
+
model_size: ModelSizeOrUnits,
|
|
204
|
+
cardinalities: dict[str, int],
|
|
205
|
+
device: torch.device,
|
|
206
|
+
):
|
|
207
|
+
super().__init__()
|
|
208
|
+
|
|
209
|
+
self.model_size = model_size
|
|
210
|
+
self.cardinalities = cardinalities
|
|
211
|
+
self.device = device
|
|
212
|
+
|
|
213
|
+
self.dims = []
|
|
214
|
+
|
|
215
|
+
self.embedders = nn.ModuleDict()
|
|
216
|
+
|
|
217
|
+
# embedding layers for each sub column defined in cardinalities
|
|
218
|
+
for sub_col, dim_input in self.cardinalities.items():
|
|
219
|
+
dim_output = _embedding_heuristic(id=self.id(sub_col), model_size=model_size, dim_input=dim_input)
|
|
220
|
+
embedder = nn.Embedding(num_embeddings=dim_input, embedding_dim=dim_output, device=device)
|
|
221
|
+
self.add(sub_column=sub_col, embedder=embedder)
|
|
222
|
+
self.dims.append(dim_output)
|
|
223
|
+
|
|
224
|
+
def __bool__(self):
|
|
225
|
+
return bool(self.embedders)
|
|
226
|
+
|
|
227
|
+
@staticmethod
|
|
228
|
+
def id(sub_column: str) -> str:
|
|
229
|
+
return f"embedder@{sub_column}"
|
|
230
|
+
|
|
231
|
+
def get(self, sub_column: str) -> nn.Module:
|
|
232
|
+
return self.embedders[self.id(sub_column)]
|
|
233
|
+
|
|
234
|
+
def add(self, sub_column: str, embedder: nn.Module) -> None:
|
|
235
|
+
self.embedders[self.id(sub_column)] = embedder
|
|
236
|
+
|
|
237
|
+
def forward(self, x) -> dict[str, torch.Tensor]:
|
|
238
|
+
# pass through sub column embedders
|
|
239
|
+
embeddings = {}
|
|
240
|
+
for sub_col in self.cardinalities:
|
|
241
|
+
xs = torch.as_tensor(x[sub_col], device=self.device)
|
|
242
|
+
if xs.is_nested: # account for nested tensors
|
|
243
|
+
xs = torch.nested.to_padded_tensor(xs, 0)
|
|
244
|
+
xs = self.get(sub_col)(xs)
|
|
245
|
+
xs = torch.squeeze(xs, -2)
|
|
246
|
+
embeddings[sub_col] = xs
|
|
247
|
+
return embeddings
|
|
248
|
+
|
|
249
|
+
def zero_mask(self, *first_dims: int) -> dict[str, torch.Tensor]:
|
|
250
|
+
# zero mask with shapes like embeddings
|
|
251
|
+
embeddings = {}
|
|
252
|
+
for idx, sub_col in enumerate(self.cardinalities):
|
|
253
|
+
last_dim = (self.dims[idx],)
|
|
254
|
+
shape = first_dims + last_dim
|
|
255
|
+
embeddings[sub_col] = torch.zeros(shape, device=self.device)
|
|
256
|
+
return embeddings
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class SequentialContextEmbedders(Embedders):
|
|
260
|
+
def __init__(
|
|
261
|
+
self,
|
|
262
|
+
model_size: ModelSizeOrUnits,
|
|
263
|
+
cardinalities: dict[str, int],
|
|
264
|
+
device: torch.device,
|
|
265
|
+
):
|
|
266
|
+
super().__init__(model_size, cardinalities, device)
|
|
267
|
+
|
|
268
|
+
def forward(self, x) -> tuple[dict[str, torch.Tensor], torch.Tensor]:
|
|
269
|
+
# pass through sub column embedders
|
|
270
|
+
embeddings = {}
|
|
271
|
+
mask = None
|
|
272
|
+
for sub_col in self.cardinalities:
|
|
273
|
+
xs = torch.as_tensor(x[sub_col], device=self.device)
|
|
274
|
+
xs = torch.nested.to_padded_tensor(xs, padding=-1)
|
|
275
|
+
mask = (xs != -1).squeeze(-1)
|
|
276
|
+
xs = torch.where(xs == -1, torch.tensor(0), xs)
|
|
277
|
+
xs = self.get(sub_col)(xs)
|
|
278
|
+
xs = torch.squeeze(xs, -2)
|
|
279
|
+
embeddings[sub_col] = xs
|
|
280
|
+
assert mask is not None
|
|
281
|
+
return embeddings, mask
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class ColumnEmbedders(nn.Module):
|
|
285
|
+
def __init__(
|
|
286
|
+
self,
|
|
287
|
+
model_size: ModelSizeOrUnits,
|
|
288
|
+
cardinalities: dict[str, int],
|
|
289
|
+
embedding_dims: list[int],
|
|
290
|
+
device: torch.device,
|
|
291
|
+
):
|
|
292
|
+
super().__init__()
|
|
293
|
+
|
|
294
|
+
self.model_size = model_size
|
|
295
|
+
self.cardinalities = cardinalities
|
|
296
|
+
self.embedding_dims = embedding_dims
|
|
297
|
+
self.device = device
|
|
298
|
+
|
|
299
|
+
self.column_sub_columns = get_sub_columns_nested_from_cardinalities(cardinalities, groupby="columns")
|
|
300
|
+
|
|
301
|
+
self.dims = []
|
|
302
|
+
|
|
303
|
+
self.column_embedders = nn.ModuleDict()
|
|
304
|
+
|
|
305
|
+
# column embeddings for each column defined in cardinalities
|
|
306
|
+
# only consider adding compressor layer if there are more than 50 sub columns
|
|
307
|
+
compressor_enabled = len(self.cardinalities) > 50
|
|
308
|
+
offset = 0
|
|
309
|
+
for column, sub_cols in self.column_sub_columns.items():
|
|
310
|
+
n_sub_cols = len(sub_cols)
|
|
311
|
+
sub_cols_dims = self.embedding_dims[offset : offset + n_sub_cols]
|
|
312
|
+
dim_input = sum(sub_cols_dims)
|
|
313
|
+
dim_output = _column_embedding_heuristic(
|
|
314
|
+
id=self.id(column),
|
|
315
|
+
model_size=self.model_size,
|
|
316
|
+
dim_input=dim_input,
|
|
317
|
+
n_sub_cols=n_sub_cols,
|
|
318
|
+
compress_enabled=compressor_enabled,
|
|
319
|
+
)
|
|
320
|
+
column_embedder = (
|
|
321
|
+
nn.Linear(in_features=dim_input, out_features=dim_output, device=self.device)
|
|
322
|
+
# apply compression only if dim_output < dim_input
|
|
323
|
+
if dim_output < dim_input
|
|
324
|
+
else nn.Identity()
|
|
325
|
+
)
|
|
326
|
+
self.add(column=column, column_embedder=column_embedder)
|
|
327
|
+
self.dims.append(dim_output)
|
|
328
|
+
offset += n_sub_cols
|
|
329
|
+
|
|
330
|
+
@staticmethod
|
|
331
|
+
def id(column: str) -> str:
|
|
332
|
+
return f"column_embedder@{column}"
|
|
333
|
+
|
|
334
|
+
def get(self, column: str) -> nn.Module:
|
|
335
|
+
return self.column_embedders[self.id(column)]
|
|
336
|
+
|
|
337
|
+
def add(self, column: str, column_embedder: nn.Module) -> None:
|
|
338
|
+
self.column_embedders[self.id(column)] = column_embedder
|
|
339
|
+
|
|
340
|
+
def forward(self, x) -> dict[str, torch.Tensor]:
|
|
341
|
+
# pass through column embedders
|
|
342
|
+
column_embeddings = {}
|
|
343
|
+
for column, sub_cols in self.column_sub_columns.items():
|
|
344
|
+
sub_cols_embeddings = torch.cat([x[sub_col] for sub_col in sub_cols], dim=-1)
|
|
345
|
+
column_embeddings[column] = self.get(column)(sub_cols_embeddings)
|
|
346
|
+
|
|
347
|
+
return column_embeddings
|
|
348
|
+
|
|
349
|
+
def zero_mask(self, *first_dims: int) -> dict[str, torch.Tensor]:
|
|
350
|
+
# zero mask with shapes like column embeddings
|
|
351
|
+
column_embeddings = {}
|
|
352
|
+
for idx, column in enumerate(self.column_sub_columns):
|
|
353
|
+
last_dim = (self.dims[idx],)
|
|
354
|
+
shape = first_dims + last_dim
|
|
355
|
+
column_embeddings[column] = torch.zeros(shape, device=self.device)
|
|
356
|
+
return column_embeddings
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class FlatContextCompressor(nn.Module):
|
|
360
|
+
def __init__(
|
|
361
|
+
self,
|
|
362
|
+
model_size: ModelSizeOrUnits,
|
|
363
|
+
ctxflt_cardinalities: dict[str, int],
|
|
364
|
+
device: torch.device,
|
|
365
|
+
):
|
|
366
|
+
super().__init__()
|
|
367
|
+
|
|
368
|
+
self.model_size = model_size
|
|
369
|
+
self.ctxflt_cardinalities = ctxflt_cardinalities
|
|
370
|
+
self.device = device
|
|
371
|
+
|
|
372
|
+
self.compressor_layers = nn.ModuleDict()
|
|
373
|
+
self.dropout = nn.Dropout(p=0.25)
|
|
374
|
+
|
|
375
|
+
# flat context embedding layers
|
|
376
|
+
self.embedders = Embedders(
|
|
377
|
+
model_size=self.model_size,
|
|
378
|
+
cardinalities=self.ctxflt_cardinalities,
|
|
379
|
+
device=self.device,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# flat context compressor layers
|
|
383
|
+
self.dim_output = 0
|
|
384
|
+
if self.embedders:
|
|
385
|
+
dim_input = sum(self.embedders.dims)
|
|
386
|
+
dims = _flat_context_heuristic(id=self.id(), model_size=model_size, dim_input=dim_input)
|
|
387
|
+
compressor_layers = nn.ModuleList()
|
|
388
|
+
for dim_in, dim_out in zip([dim_input] + dims[:-1], dims):
|
|
389
|
+
compressor_layers.append(nn.Linear(in_features=dim_in, out_features=dim_out, device=device))
|
|
390
|
+
self.set(compressor_layers)
|
|
391
|
+
self.dim_output = dims[-1]
|
|
392
|
+
|
|
393
|
+
@staticmethod
|
|
394
|
+
def id() -> str:
|
|
395
|
+
return "flat_context@"
|
|
396
|
+
|
|
397
|
+
def get(self) -> nn.ModuleList:
|
|
398
|
+
return self.compressor_layers[self.id()]
|
|
399
|
+
|
|
400
|
+
def set(self, compressor: nn.ModuleList) -> None:
|
|
401
|
+
self.compressor_layers[self.id()] = compressor
|
|
402
|
+
|
|
403
|
+
def forward(self, x) -> list[torch.Tensor]:
|
|
404
|
+
# pass through flat context embeddings
|
|
405
|
+
embeddings = self.embedders(x)
|
|
406
|
+
|
|
407
|
+
# pass through multi-layer flat context compressor
|
|
408
|
+
flat_context = []
|
|
409
|
+
if embeddings:
|
|
410
|
+
xs = torch.cat(list(embeddings.values()), dim=-1)
|
|
411
|
+
for compressor_layer in self.get():
|
|
412
|
+
xs = compressor_layer(xs)
|
|
413
|
+
xs = self.dropout(xs)
|
|
414
|
+
flat_context = [xs]
|
|
415
|
+
return flat_context
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class SequentialContextCompressor(nn.Module):
|
|
419
|
+
def __init__(
|
|
420
|
+
self,
|
|
421
|
+
model_size: ModelSizeOrUnits,
|
|
422
|
+
ctxseq_cardinalities: dict[str, int],
|
|
423
|
+
ctxseq_len_median: dict[str, int],
|
|
424
|
+
device: torch.device,
|
|
425
|
+
with_dp: bool = False,
|
|
426
|
+
):
|
|
427
|
+
super().__init__()
|
|
428
|
+
|
|
429
|
+
self.model_size = model_size
|
|
430
|
+
self.ctxseq_cardinalities = ctxseq_cardinalities
|
|
431
|
+
self.ctxseq_len_median = ctxseq_len_median
|
|
432
|
+
self.device = device
|
|
433
|
+
dropout_rate = 0.25
|
|
434
|
+
|
|
435
|
+
self.ctxseq_table_sub_columns = get_sub_columns_nested_from_cardinalities(
|
|
436
|
+
self.ctxseq_cardinalities, groupby="tables"
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
self.embedders = nn.ModuleDict()
|
|
440
|
+
self.compressor_layers = nn.ModuleDict()
|
|
441
|
+
self.dropout = nn.Dropout(p=dropout_rate)
|
|
442
|
+
|
|
443
|
+
self.dim_outputs = []
|
|
444
|
+
for table, sub_columns in self.ctxseq_table_sub_columns.items():
|
|
445
|
+
# sequential context table embedding layers
|
|
446
|
+
table_cardinalities = {
|
|
447
|
+
sub_col: card for sub_col, card in self.ctxseq_cardinalities.items() if sub_col in sub_columns
|
|
448
|
+
}
|
|
449
|
+
table_embedders = SequentialContextEmbedders(
|
|
450
|
+
model_size=self.model_size,
|
|
451
|
+
cardinalities=table_cardinalities,
|
|
452
|
+
device=self.device,
|
|
453
|
+
)
|
|
454
|
+
self.embedders[table] = table_embedders
|
|
455
|
+
|
|
456
|
+
# sequential context table compressor layers
|
|
457
|
+
dim_input = sum(table_embedders.dims)
|
|
458
|
+
dims = _sequential_context_heuristic(
|
|
459
|
+
id=self.id(table),
|
|
460
|
+
model_size=self.model_size,
|
|
461
|
+
dim_input=dim_input,
|
|
462
|
+
seq_len_median=ctxseq_len_median.get(table, 1),
|
|
463
|
+
)
|
|
464
|
+
hidden_size = dims[-1]
|
|
465
|
+
num_layers = len(dims)
|
|
466
|
+
bidirectional = True
|
|
467
|
+
lstm_cls = DPLSTM if with_dp else partial(nn.LSTM, device=self.device)
|
|
468
|
+
table_compressor_layer = lstm_cls(
|
|
469
|
+
input_size=dim_input,
|
|
470
|
+
hidden_size=hidden_size,
|
|
471
|
+
num_layers=num_layers,
|
|
472
|
+
dropout=dropout_rate if num_layers > 1 else 0.0,
|
|
473
|
+
batch_first=True,
|
|
474
|
+
bidirectional=bidirectional,
|
|
475
|
+
)
|
|
476
|
+
self.add(table=table, compressor_layer=table_compressor_layer)
|
|
477
|
+
self.dim_outputs.append(hidden_size * (2 if bidirectional else 1))
|
|
478
|
+
|
|
479
|
+
@staticmethod
|
|
480
|
+
def id(table: str) -> str:
|
|
481
|
+
return f"sequential_context@{table}"
|
|
482
|
+
|
|
483
|
+
def get(self, table: str) -> nn.Module:
|
|
484
|
+
return self.compressor_layers[self.id(table)]
|
|
485
|
+
|
|
486
|
+
def add(self, table: str, compressor_layer: nn.Module) -> None:
|
|
487
|
+
self.compressor_layers[self.id(table)] = compressor_layer
|
|
488
|
+
|
|
489
|
+
def forward(self, x) -> tuple[list[torch.Tensor], list[torch.Tensor]]:
|
|
490
|
+
sequential_contexts = []
|
|
491
|
+
sequential_context_masks = []
|
|
492
|
+
for table, _ in self.ctxseq_table_sub_columns.items():
|
|
493
|
+
# pass through sequential context table embeddings
|
|
494
|
+
table_embeddings, mask = self.embedders[table](x)
|
|
495
|
+
sequential_context_masks.append(mask)
|
|
496
|
+
# pass through multi-layer sequential context table compressor
|
|
497
|
+
xs = torch.cat(list(table_embeddings.values()), dim=-1)
|
|
498
|
+
compressor_layer = self.get(table)
|
|
499
|
+
xs = self.dropout(xs)
|
|
500
|
+
lengths = torch.sum(mask, dim=-1)
|
|
501
|
+
packed_xs = nn.utils.rnn.pack_padded_sequence(xs, lengths.cpu(), batch_first=True, enforce_sorted=False)
|
|
502
|
+
packed_xs, _ = compressor_layer(packed_xs)
|
|
503
|
+
xs, _ = nn.utils.rnn.pad_packed_sequence(packed_xs, batch_first=True)
|
|
504
|
+
|
|
505
|
+
# we return the full sequence of hidden states
|
|
506
|
+
sequential_contexts.append(xs)
|
|
507
|
+
|
|
508
|
+
return sequential_contexts, sequential_context_masks
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
class ContextCompressor(nn.Module):
|
|
512
|
+
def __init__(
|
|
513
|
+
self,
|
|
514
|
+
model_size: ModelSizeOrUnits,
|
|
515
|
+
ctx_cardinalities: dict[str, int],
|
|
516
|
+
ctxseq_len_median: dict[str, int],
|
|
517
|
+
device: torch.device,
|
|
518
|
+
with_dp: bool = False,
|
|
519
|
+
):
|
|
520
|
+
super().__init__()
|
|
521
|
+
self.model_size = model_size
|
|
522
|
+
self.ctxflt_cardinalities = {k: v for k, v in ctx_cardinalities.items() if k.startswith(CTXFLT)}
|
|
523
|
+
self.ctxseq_cardinalities = {k: v for k, v in ctx_cardinalities.items() if k.startswith(CTXSEQ)}
|
|
524
|
+
self.ctxseq_len_median = ctxseq_len_median
|
|
525
|
+
self.device = device
|
|
526
|
+
|
|
527
|
+
# flat context
|
|
528
|
+
self.flat_context_compressor = FlatContextCompressor(
|
|
529
|
+
model_size=self.model_size,
|
|
530
|
+
ctxflt_cardinalities=self.ctxflt_cardinalities,
|
|
531
|
+
device=device,
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
# sequential context(s)
|
|
535
|
+
self.sequential_context_compressor = SequentialContextCompressor(
|
|
536
|
+
model_size=self.model_size,
|
|
537
|
+
ctxseq_cardinalities=self.ctxseq_cardinalities,
|
|
538
|
+
ctxseq_len_median=self.ctxseq_len_median,
|
|
539
|
+
device=device,
|
|
540
|
+
with_dp=with_dp,
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
# size of context output
|
|
544
|
+
self.dim_output = self.flat_context_compressor.dim_output + sum(self.sequential_context_compressor.dim_outputs)
|
|
545
|
+
|
|
546
|
+
def forward(self, x) -> tuple[list[torch.Tensor], list[torch.Tensor], torch.Tensor]:
|
|
547
|
+
flat_context = self.flat_context_compressor(x)
|
|
548
|
+
sequential_contexts, sequential_context_masks = self.sequential_context_compressor(x)
|
|
549
|
+
return flat_context, sequential_contexts, sequential_context_masks
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
class HistoryCompressor(nn.Module):
|
|
553
|
+
def __init__(
|
|
554
|
+
self,
|
|
555
|
+
model_size: ModelSizeOrUnits,
|
|
556
|
+
embedding_dims: list[int],
|
|
557
|
+
seq_len_median: int,
|
|
558
|
+
device: torch.device,
|
|
559
|
+
with_dp: bool = False,
|
|
560
|
+
):
|
|
561
|
+
super().__init__()
|
|
562
|
+
|
|
563
|
+
self.model_size = model_size
|
|
564
|
+
self.embedding_dims = embedding_dims
|
|
565
|
+
self.seq_len_median = seq_len_median
|
|
566
|
+
self.device = device
|
|
567
|
+
dropout_rate = 0.25
|
|
568
|
+
|
|
569
|
+
self.compressor_layers = nn.ModuleDict()
|
|
570
|
+
self.dropout = nn.Dropout(p=dropout_rate)
|
|
571
|
+
|
|
572
|
+
dim_input = sum(embedding_dims)
|
|
573
|
+
dims = _history_heuristic(
|
|
574
|
+
id=self.id(),
|
|
575
|
+
model_size=self.model_size,
|
|
576
|
+
dim_input=dim_input,
|
|
577
|
+
seq_len_median=self.seq_len_median,
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
hidden_size = dims[-1]
|
|
581
|
+
num_layers = len(dims)
|
|
582
|
+
lstm_cls = DPLSTM if with_dp else nn.LSTM
|
|
583
|
+
compressor_layer = lstm_cls(
|
|
584
|
+
input_size=dim_input,
|
|
585
|
+
hidden_size=hidden_size,
|
|
586
|
+
num_layers=num_layers,
|
|
587
|
+
dropout=dropout_rate if num_layers > 1 else 0.0,
|
|
588
|
+
batch_first=True,
|
|
589
|
+
)
|
|
590
|
+
self.set(compressor_layer)
|
|
591
|
+
self.dim_output = hidden_size
|
|
592
|
+
|
|
593
|
+
@staticmethod
|
|
594
|
+
def id() -> str:
|
|
595
|
+
return "history@"
|
|
596
|
+
|
|
597
|
+
def get(self) -> nn.Module:
|
|
598
|
+
return self.compressor_layers[self.id()]
|
|
599
|
+
|
|
600
|
+
def set(self, compressor_layer: nn.Module) -> None:
|
|
601
|
+
self.compressor_layers[self.id()] = compressor_layer
|
|
602
|
+
|
|
603
|
+
def forward(self, x, history_state=None) -> tuple[torch.Tensor, torch.Tensor]:
|
|
604
|
+
compressor_layer = self.get()
|
|
605
|
+
x = self.dropout(x)
|
|
606
|
+
x, history_state = compressor_layer(x, history_state)
|
|
607
|
+
return x, history_state
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
class Regressors(nn.Module):
|
|
611
|
+
def __init__(
|
|
612
|
+
self,
|
|
613
|
+
*,
|
|
614
|
+
model_size: ModelSizeOrUnits,
|
|
615
|
+
cardinalities: dict[str, int],
|
|
616
|
+
context_dim: int,
|
|
617
|
+
history_dim: int | None = None,
|
|
618
|
+
embedding_dims: list[int],
|
|
619
|
+
column_embedding_dims: list[int],
|
|
620
|
+
device: torch.device,
|
|
621
|
+
):
|
|
622
|
+
super().__init__()
|
|
623
|
+
|
|
624
|
+
self.model_size = model_size
|
|
625
|
+
self.cardinalities = cardinalities
|
|
626
|
+
self.context_dim = context_dim
|
|
627
|
+
self.history_dim = history_dim
|
|
628
|
+
self.column_embedding_dims = column_embedding_dims
|
|
629
|
+
self.embedding_dims = embedding_dims
|
|
630
|
+
self.device = device
|
|
631
|
+
|
|
632
|
+
self.column_sub_columns = get_sub_columns_nested_from_cardinalities(self.cardinalities, groupby="columns")
|
|
633
|
+
self.sub_columns_lookup = get_sub_columns_lookup(self.column_sub_columns)
|
|
634
|
+
|
|
635
|
+
self.dims_output = {}
|
|
636
|
+
|
|
637
|
+
self.regressors = nn.ModuleDict()
|
|
638
|
+
self.dropout = nn.Dropout(p=0.25)
|
|
639
|
+
|
|
640
|
+
for sub_column, lookup in self.sub_columns_lookup.items():
|
|
641
|
+
# collect previous sub column embedding dims for current column
|
|
642
|
+
prev_embedding_dims = self.embedding_dims[lookup.sub_col_offset : lookup.sub_col_cum]
|
|
643
|
+
|
|
644
|
+
dim_input = (
|
|
645
|
+
self.context_dim
|
|
646
|
+
+ (self.history_dim or 0) # history is not present in FlatModel
|
|
647
|
+
+ sum(self.column_embedding_dims)
|
|
648
|
+
+ sum(prev_embedding_dims)
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
# multi-layer sub column regressor
|
|
652
|
+
dims = _regressor_heuristic(
|
|
653
|
+
id=self.id(sub_column),
|
|
654
|
+
model_size=self.model_size,
|
|
655
|
+
dim_input=dim_input,
|
|
656
|
+
cardinality=self.cardinalities[sub_column],
|
|
657
|
+
)
|
|
658
|
+
regressor_layers = nn.ModuleList()
|
|
659
|
+
for dim_in, dim_out in zip([dim_input] + dims[:-1], dims):
|
|
660
|
+
regressor_layers.append(nn.Linear(in_features=dim_in, out_features=dim_out, device=self.device))
|
|
661
|
+
self.add(sub_column=sub_column, regressor=regressor_layers)
|
|
662
|
+
self.dims_output[sub_column] = dims[-1]
|
|
663
|
+
|
|
664
|
+
@staticmethod
|
|
665
|
+
def id(sub_column: str) -> str:
|
|
666
|
+
return f"regressor@{sub_column}"
|
|
667
|
+
|
|
668
|
+
def get(self, sub_column: str) -> nn.ModuleList:
|
|
669
|
+
return self.regressors[self.id(sub_column)]
|
|
670
|
+
|
|
671
|
+
def add(self, sub_column: str, regressor: nn.ModuleList) -> None:
|
|
672
|
+
self.regressors[self.id(sub_column)] = regressor
|
|
673
|
+
|
|
674
|
+
def forward(self, regressor_in: list[torch.Tensor], sub_col: str) -> torch.Tensor:
|
|
675
|
+
x = torch.cat(regressor_in, dim=-1)
|
|
676
|
+
# pass through multi-layer sub column regressor
|
|
677
|
+
for regressor_layer in self.get(sub_col):
|
|
678
|
+
x = self.dropout(x)
|
|
679
|
+
x = regressor_layer(x)
|
|
680
|
+
x = nn.ReLU()(x)
|
|
681
|
+
return x
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
class Predictors(nn.Module):
|
|
685
|
+
def __init__(
|
|
686
|
+
self,
|
|
687
|
+
cardinalities: dict[str, int],
|
|
688
|
+
regressors_dims: dict[str, int],
|
|
689
|
+
device: torch.device,
|
|
690
|
+
):
|
|
691
|
+
super().__init__()
|
|
692
|
+
|
|
693
|
+
self.cardinalities = cardinalities
|
|
694
|
+
self.regressors_dims = regressors_dims
|
|
695
|
+
self.device = device
|
|
696
|
+
|
|
697
|
+
self.predictors = nn.ModuleDict()
|
|
698
|
+
|
|
699
|
+
for sub_col, dim_output in self.cardinalities.items():
|
|
700
|
+
dim_input = self.regressors_dims[sub_col]
|
|
701
|
+
self.predictors[sub_col] = nn.Linear(in_features=dim_input, out_features=dim_output, device=self.device)
|
|
702
|
+
|
|
703
|
+
def forward(self, x: torch.Tensor, sub_col: str) -> torch.Tensor:
|
|
704
|
+
return self.predictors[sub_col](x)
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def _make_permutation_mask(
|
|
708
|
+
col_embedding_dims: list[int],
|
|
709
|
+
columns: list[str],
|
|
710
|
+
column_order: list[str] | None,
|
|
711
|
+
is_sequential: bool,
|
|
712
|
+
device: torch.device,
|
|
713
|
+
) -> torch.Tensor:
|
|
714
|
+
n_cols = len(columns)
|
|
715
|
+
if column_order is not None:
|
|
716
|
+
# create mask in provided order
|
|
717
|
+
order = torch.tensor([columns.index(c) for c in column_order], dtype=torch.int32)
|
|
718
|
+
elif is_sequential and n_cols >= 1:
|
|
719
|
+
# create mask in random order, but keep SLEN/SIDX/SDEC column at first position
|
|
720
|
+
order = torch.randperm(n_cols - 1) + 1
|
|
721
|
+
order = torch.cat((torch.zeros(1, dtype=torch.int32), order), dim=0)
|
|
722
|
+
else:
|
|
723
|
+
# create mask in random order
|
|
724
|
+
order = torch.randperm(n_cols)
|
|
725
|
+
|
|
726
|
+
# convert order into a binary mask consisting of 0s and 1s
|
|
727
|
+
idx = np.argsort(order)
|
|
728
|
+
ones = torch.ones(n_cols, n_cols, dtype=torch.int32, device=device)
|
|
729
|
+
mask = torch.tril(ones, diagonal=-1) # strict lower triangular matrix
|
|
730
|
+
mask = mask[idx, :][:, idx].bool() # re-order rows and columns
|
|
731
|
+
reps = torch.as_tensor(col_embedding_dims, dtype=torch.int32, device=device)
|
|
732
|
+
mask = torch.repeat_interleave(mask, repeats=reps, dim=1) # expand columns
|
|
733
|
+
return mask
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
def _sampling_temperature(probs: torch.Tensor, temperature: float) -> torch.Tensor:
|
|
737
|
+
temperature = torch.max(torch.tensor(temperature, device=probs.device), torch.tensor(1e-3, device=probs.device))
|
|
738
|
+
# compute softmax with temperature scaling
|
|
739
|
+
scaled_logits = torch.divide(torch.log(probs), temperature)
|
|
740
|
+
probs = torch.nn.functional.softmax(scaled_logits, dim=-1)
|
|
741
|
+
return probs
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def _sampling_nucleus(probs: torch.Tensor, top_p: float) -> torch.Tensor:
|
|
745
|
+
"""
|
|
746
|
+
Implements nucleus sampling (https://arxiv.org/abs/1904.09751)
|
|
747
|
+
Parameters:
|
|
748
|
+
probs: probabilities of tokens to sample from
|
|
749
|
+
top_p: cumulative probability of top p tokens to sample from
|
|
750
|
+
"""
|
|
751
|
+
# sort probabilities in descending order and get their indices
|
|
752
|
+
sorted_indices = torch.argsort(input=probs, descending=True, dim=-1)
|
|
753
|
+
sorted_probs = torch.gather(input=probs, dim=-1, index=sorted_indices)
|
|
754
|
+
|
|
755
|
+
# compute the cumulative sum of the sorted probabilities
|
|
756
|
+
cumulative_probs = torch.cumsum(input=sorted_probs, dim=-1)
|
|
757
|
+
|
|
758
|
+
# remove tokens whose cumulative probability exceeds the threshold
|
|
759
|
+
sorted_indices_to_remove = torch.greater(cumulative_probs, top_p)
|
|
760
|
+
|
|
761
|
+
# shift the indices to the right to keep the first token above the threshold
|
|
762
|
+
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
|
|
763
|
+
# we need at least one token to sample from
|
|
764
|
+
sorted_indices_to_remove[..., 0] = False
|
|
765
|
+
|
|
766
|
+
# scatter sorted tensors to original indexing
|
|
767
|
+
_, reversed_indices = torch.sort(input=sorted_indices, dim=-1)
|
|
768
|
+
indices_to_remove = torch.gather(input=sorted_indices_to_remove, dim=-1, index=reversed_indices)
|
|
769
|
+
|
|
770
|
+
# zero out probabilities that need to be removed
|
|
771
|
+
probs = probs.masked_fill(mask=indices_to_remove, value=0.0)
|
|
772
|
+
|
|
773
|
+
# re-normalize to [0, 1]
|
|
774
|
+
# if all probs are zero (first sorted / biggest is zero), fallback to uniform sampling
|
|
775
|
+
sums = torch.sum(probs, dim=-1, keepdim=True)
|
|
776
|
+
probs = torch.where(sums > 0.0, probs / sums, torch.ones_like(probs) / probs.size(-1))
|
|
777
|
+
|
|
778
|
+
return probs
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
def _sampling_fixed_probs(probs: torch.Tensor, fixed_probs: dict[int, float]) -> torch.Tensor:
|
|
782
|
+
# adding small epsilon avoids edge case where non-fixed values are all zero
|
|
783
|
+
probs = probs + 1.0e-20
|
|
784
|
+
|
|
785
|
+
# identify fixed and non-fixed indices
|
|
786
|
+
fixed_indices = torch.as_tensor(list(fixed_probs.keys()), device=probs.device)
|
|
787
|
+
fixed_values = torch.as_tensor(list(fixed_probs.values()), device=probs.device)
|
|
788
|
+
fixed_mask = torch.zeros(probs.size(-1), dtype=torch.bool, device=probs.device)
|
|
789
|
+
fixed_mask[fixed_indices] = True
|
|
790
|
+
non_fixed_mask = ~fixed_mask
|
|
791
|
+
|
|
792
|
+
# handle edge case: all indices are fixed to 0.0
|
|
793
|
+
if fixed_mask.all() and fixed_values.sum() == 0.0:
|
|
794
|
+
probs[:] = 1.0 / probs.size(-1)
|
|
795
|
+
return probs
|
|
796
|
+
|
|
797
|
+
# overwrite probs under fixed indices
|
|
798
|
+
probs[..., fixed_indices] = fixed_values.unsqueeze(0)
|
|
799
|
+
|
|
800
|
+
# normalize non-fixed indices
|
|
801
|
+
remaining_prob = torch.clamp(1.0 - fixed_values.sum(), min=0.0)
|
|
802
|
+
non_fixed_sum = torch.sum(probs[..., non_fixed_mask], dim=-1, keepdim=True)
|
|
803
|
+
probs[..., non_fixed_mask] = probs[..., non_fixed_mask] * (remaining_prob / non_fixed_sum)
|
|
804
|
+
|
|
805
|
+
return probs
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
def _sample(
|
|
809
|
+
probs: torch.Tensor,
|
|
810
|
+
temperature: float | None = None,
|
|
811
|
+
top_p: float | None = None,
|
|
812
|
+
fixed_probs: dict[int, float] | None = None,
|
|
813
|
+
) -> torch.Tensor:
|
|
814
|
+
if temperature is not None and temperature != 1.0:
|
|
815
|
+
probs = _sampling_temperature(probs, temperature)
|
|
816
|
+
|
|
817
|
+
if top_p is not None and top_p < 1.0:
|
|
818
|
+
probs = _sampling_nucleus(probs, top_p)
|
|
819
|
+
|
|
820
|
+
if fixed_probs is not None:
|
|
821
|
+
probs = _sampling_fixed_probs(probs, fixed_probs)
|
|
822
|
+
|
|
823
|
+
# ensure that probabilities are valid
|
|
824
|
+
# log cases where this isn't the case to help understand when this can happen
|
|
825
|
+
is_nan = torch.isnan(probs)
|
|
826
|
+
is_neg = probs < 0.0
|
|
827
|
+
is_gt1 = probs > 1.0
|
|
828
|
+
if is_nan.any():
|
|
829
|
+
nan_count = is_nan.sum().item()
|
|
830
|
+
_LOG.warning(f"[ARGN] Set {nan_count} probabilities from NaN to 1 in {probs.shape}")
|
|
831
|
+
probs = torch.nan_to_num(probs, nan=1.0)
|
|
832
|
+
if is_neg.any():
|
|
833
|
+
neg_count = is_neg.sum().item()
|
|
834
|
+
_LOG.warning(f"[ARGN] Clip {neg_count} probabilities below 0 in {probs.shape}")
|
|
835
|
+
probs = torch.clamp(probs, min=0.0)
|
|
836
|
+
if is_gt1.any():
|
|
837
|
+
gt1_count = is_gt1.sum().item()
|
|
838
|
+
_LOG.warning(f"[ARGN] Clip {gt1_count} probabilities above 1 in {probs.shape}")
|
|
839
|
+
probs = torch.clamp(probs, max=1.0)
|
|
840
|
+
|
|
841
|
+
probs = torch.multinomial(probs, num_samples=1, replacement=True)
|
|
842
|
+
return probs
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
##########################
|
|
846
|
+
### TabularARGN models ###
|
|
847
|
+
##########################
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
class FlatModel(nn.Module):
|
|
851
|
+
def __init__(
|
|
852
|
+
self,
|
|
853
|
+
tgt_cardinalities: dict[str, int],
|
|
854
|
+
ctx_cardinalities: dict[str, int],
|
|
855
|
+
ctxseq_len_median: dict[str, int],
|
|
856
|
+
model_size: ModelSizeOrUnits,
|
|
857
|
+
column_order: list[str] | None,
|
|
858
|
+
device: torch.device,
|
|
859
|
+
):
|
|
860
|
+
super().__init__()
|
|
861
|
+
|
|
862
|
+
self.tgt_cardinalities = tgt_cardinalities
|
|
863
|
+
self.tgt_sub_columns = get_sub_columns_from_cardinalities(tgt_cardinalities)
|
|
864
|
+
self.tgt_columns = get_columns_from_cardinalities(tgt_cardinalities)
|
|
865
|
+
self.tgt_column_sub_columns = get_sub_columns_nested_from_cardinalities(tgt_cardinalities, groupby="columns")
|
|
866
|
+
self.last_sub_cols = [v[-1] for v in self.tgt_column_sub_columns.values()]
|
|
867
|
+
self.tgt_sub_columns_lookup = get_sub_columns_lookup(self.tgt_column_sub_columns)
|
|
868
|
+
self.ctx_cardinalities = ctx_cardinalities
|
|
869
|
+
self.ctxseq_len_median = ctxseq_len_median
|
|
870
|
+
self.model_size = model_size
|
|
871
|
+
self.column_order = column_order
|
|
872
|
+
self.device = device or torch.device("cpu")
|
|
873
|
+
|
|
874
|
+
# context
|
|
875
|
+
self.context_compressor = ContextCompressor(
|
|
876
|
+
model_size=self.model_size,
|
|
877
|
+
ctx_cardinalities=self.ctx_cardinalities,
|
|
878
|
+
ctxseq_len_median=self.ctxseq_len_median,
|
|
879
|
+
device=device,
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
# sub column embeddings
|
|
883
|
+
self.embedders = Embedders(
|
|
884
|
+
model_size=self.model_size,
|
|
885
|
+
cardinalities=self.tgt_cardinalities,
|
|
886
|
+
device=self.device,
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
# column embeddings
|
|
890
|
+
self.column_embedders = ColumnEmbedders(
|
|
891
|
+
model_size=self.model_size,
|
|
892
|
+
cardinalities=self.tgt_cardinalities,
|
|
893
|
+
embedding_dims=self.embedders.dims,
|
|
894
|
+
device=self.device,
|
|
895
|
+
)
|
|
896
|
+
|
|
897
|
+
# regressors
|
|
898
|
+
self.regressors = Regressors(
|
|
899
|
+
model_size=self.model_size,
|
|
900
|
+
cardinalities=self.tgt_cardinalities,
|
|
901
|
+
context_dim=self.context_compressor.dim_output,
|
|
902
|
+
embedding_dims=self.embedders.dims,
|
|
903
|
+
column_embedding_dims=self.column_embedders.dims,
|
|
904
|
+
device=self.device,
|
|
905
|
+
)
|
|
906
|
+
|
|
907
|
+
# predictors
|
|
908
|
+
self.predictors = Predictors(
|
|
909
|
+
cardinalities=self.tgt_cardinalities,
|
|
910
|
+
regressors_dims=self.regressors.dims_output,
|
|
911
|
+
device=device,
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
def _handle_context(
|
|
915
|
+
self, context: tuple[torch.Tensor, list[torch.Tensor], list[torch.Tensor]]
|
|
916
|
+
) -> list[torch.Tensor]:
|
|
917
|
+
flat_context, sequential_contexts, sequential_context_masks = context
|
|
918
|
+
|
|
919
|
+
last_state_seq_ctxs = []
|
|
920
|
+
for seq_ctx, mask in zip(sequential_contexts, sequential_context_masks):
|
|
921
|
+
lengths = torch.sum(mask, dim=1)
|
|
922
|
+
|
|
923
|
+
hidden_dim = seq_ctx.size(-1) // 2
|
|
924
|
+
forward_states = seq_ctx[..., :hidden_dim]
|
|
925
|
+
backward_states = seq_ctx[..., hidden_dim:]
|
|
926
|
+
|
|
927
|
+
# Take last valid step from forward direction and first valid step from backward direction
|
|
928
|
+
forward_final = torch.stack([forward_states[i, length - 1 : length, :] for i, length in enumerate(lengths)])
|
|
929
|
+
backward_final = backward_states[:, 0:1, :] # first timestep is last state of backward direction
|
|
930
|
+
|
|
931
|
+
xs = torch.cat([forward_final, backward_final], dim=-1)
|
|
932
|
+
xs = torch.squeeze(xs, dim=1)
|
|
933
|
+
last_state_seq_ctxs.append(xs)
|
|
934
|
+
|
|
935
|
+
context = flat_context + last_state_seq_ctxs
|
|
936
|
+
|
|
937
|
+
if len(context) > 0:
|
|
938
|
+
context = [torch.cat(context, dim=-1)]
|
|
939
|
+
|
|
940
|
+
return context
|
|
941
|
+
|
|
942
|
+
def forward(
|
|
943
|
+
self,
|
|
944
|
+
x,
|
|
945
|
+
mode: Literal["trn", "gen"],
|
|
946
|
+
batch_size: int | None = None,
|
|
947
|
+
fixed_probs=None,
|
|
948
|
+
fixed_values=None,
|
|
949
|
+
temperature: float | None = None,
|
|
950
|
+
top_p: float | None = None,
|
|
951
|
+
return_probs: list[str] | None = None,
|
|
952
|
+
fairness_transforms: dict[str, Any] | None = None,
|
|
953
|
+
) -> tuple[dict[str, torch.Tensor], dict[str, torch.Tensor]]:
|
|
954
|
+
fixed_probs = fixed_probs or {}
|
|
955
|
+
fixed_values = fixed_values or {}
|
|
956
|
+
return_probs = return_probs or []
|
|
957
|
+
outputs = {}
|
|
958
|
+
probs = {}
|
|
959
|
+
fairness_transforms = fairness_transforms or {}
|
|
960
|
+
|
|
961
|
+
if mode == "trn":
|
|
962
|
+
# forward pass through context compressor
|
|
963
|
+
context = self.context_compressor(x)
|
|
964
|
+
context = self._handle_context(context)
|
|
965
|
+
|
|
966
|
+
# forward pass through sub column embedders
|
|
967
|
+
tgt_embeds = self.embedders(x)
|
|
968
|
+
|
|
969
|
+
# forward pass through column embedders
|
|
970
|
+
tgt_col_embeds = self.column_embedders(tgt_embeds)
|
|
971
|
+
|
|
972
|
+
# create batch-wise permutation mask
|
|
973
|
+
col_embeddings = torch.cat(list(tgt_col_embeds.values()), dim=-1)
|
|
974
|
+
col_mask = _make_permutation_mask(
|
|
975
|
+
col_embedding_dims=self.column_embedders.dims,
|
|
976
|
+
columns=self.tgt_columns,
|
|
977
|
+
column_order=self.column_order,
|
|
978
|
+
is_sequential=False,
|
|
979
|
+
device=self.device,
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
for sub_col, lookup in self.tgt_sub_columns_lookup.items():
|
|
983
|
+
# mask concatenated column embeddings
|
|
984
|
+
masked_col_embeds = [torch.mul(col_mask[lookup.col_idx, :].int(), col_embeddings)]
|
|
985
|
+
|
|
986
|
+
# collect previous sub column embeddings for current column
|
|
987
|
+
prev_sub_col_embeds = [
|
|
988
|
+
tgt_embeds[sub_col] for sub_col in self.tgt_sub_columns[lookup.sub_col_offset : lookup.sub_col_cum]
|
|
989
|
+
]
|
|
990
|
+
|
|
991
|
+
# regressor
|
|
992
|
+
regressor_in = context + masked_col_embeds + prev_sub_col_embeds
|
|
993
|
+
xs = self.regressors(regressor_in, sub_col)
|
|
994
|
+
|
|
995
|
+
# predictor
|
|
996
|
+
xs = self.predictors(xs, sub_col)
|
|
997
|
+
|
|
998
|
+
# update output
|
|
999
|
+
outputs[sub_col] = xs
|
|
1000
|
+
|
|
1001
|
+
else: # mode == "gen"
|
|
1002
|
+
# forward pass through context compressor
|
|
1003
|
+
context = self.context_compressor(x)
|
|
1004
|
+
context = self._handle_context(context)
|
|
1005
|
+
|
|
1006
|
+
# initialize sub column embeddings
|
|
1007
|
+
tgt_embeds = self.embedders.zero_mask(batch_size)
|
|
1008
|
+
|
|
1009
|
+
# initialize column embeddings
|
|
1010
|
+
tgt_col_embeds = self.column_embedders.zero_mask(batch_size)
|
|
1011
|
+
|
|
1012
|
+
# concatenate column embeddings
|
|
1013
|
+
col_embeddings = torch.cat(list(tgt_col_embeds.values()), dim=-1)
|
|
1014
|
+
|
|
1015
|
+
# take sub columns in the specified generation order
|
|
1016
|
+
column_order = self.column_order or self.tgt_columns
|
|
1017
|
+
sub_column_order = [sub_col for col in column_order for sub_col in self.tgt_column_sub_columns[col]]
|
|
1018
|
+
|
|
1019
|
+
for sub_col in sub_column_order:
|
|
1020
|
+
lookup = self.tgt_sub_columns_lookup[sub_col]
|
|
1021
|
+
|
|
1022
|
+
# if sub column is fixed, skip sampling and use that value
|
|
1023
|
+
if sub_col in fixed_values:
|
|
1024
|
+
out = fixed_values[sub_col]
|
|
1025
|
+
|
|
1026
|
+
else: # sample from distribution
|
|
1027
|
+
# collect previous sub column embeddings for current column
|
|
1028
|
+
prev_sub_col_embeds = [
|
|
1029
|
+
tgt_embeds[sub_col]
|
|
1030
|
+
for sub_col in self.tgt_sub_columns[lookup.sub_col_offset : lookup.sub_col_cum]
|
|
1031
|
+
]
|
|
1032
|
+
|
|
1033
|
+
# regressor
|
|
1034
|
+
regressor_in = context + [col_embeddings] + prev_sub_col_embeds
|
|
1035
|
+
xs = self.regressors(regressor_in, sub_col)
|
|
1036
|
+
|
|
1037
|
+
# predictor
|
|
1038
|
+
xs = self.predictors(xs, sub_col)
|
|
1039
|
+
|
|
1040
|
+
# softmax to probs
|
|
1041
|
+
xs = nn.Softmax(dim=-1)(xs)
|
|
1042
|
+
|
|
1043
|
+
# keep probabilities (used e.g. for fairness)
|
|
1044
|
+
if sub_col in return_probs:
|
|
1045
|
+
probs[sub_col] = xs
|
|
1046
|
+
|
|
1047
|
+
# apply fairness transform when generating the target sub column
|
|
1048
|
+
if fairness_transforms:
|
|
1049
|
+
xs = apply_fairness_transforms(sub_col, xs, outputs, fairness_transforms)
|
|
1050
|
+
|
|
1051
|
+
# sample
|
|
1052
|
+
out = torch.squeeze(
|
|
1053
|
+
_sample(
|
|
1054
|
+
probs=xs,
|
|
1055
|
+
temperature=temperature,
|
|
1056
|
+
top_p=top_p,
|
|
1057
|
+
fixed_probs=fixed_probs.get(sub_col),
|
|
1058
|
+
),
|
|
1059
|
+
dim=-1,
|
|
1060
|
+
)
|
|
1061
|
+
|
|
1062
|
+
# update output
|
|
1063
|
+
outputs[sub_col] = out
|
|
1064
|
+
|
|
1065
|
+
# update current sub column embedding
|
|
1066
|
+
tgt_embeds[sub_col] = self.embedders.get(sub_col)(out)
|
|
1067
|
+
|
|
1068
|
+
# update current column embedding
|
|
1069
|
+
if sub_col in self.last_sub_cols:
|
|
1070
|
+
col_sub_cols = self.tgt_column_sub_columns[lookup.col_name]
|
|
1071
|
+
col_embed_in = torch.cat([tgt_embeds[sc] for sc in col_sub_cols], dim=-1)
|
|
1072
|
+
tgt_col_embeds[lookup.col_name] = self.column_embedders.get(lookup.col_name)(col_embed_in)
|
|
1073
|
+
col_embeddings = torch.cat(list(tgt_col_embeds.values()), dim=-1)
|
|
1074
|
+
|
|
1075
|
+
# order outputs according to tgt_sub_columns
|
|
1076
|
+
outputs = {sub_col: outputs[sub_col] for sub_col in self.tgt_sub_columns}
|
|
1077
|
+
|
|
1078
|
+
return outputs, probs
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
class AttentionModule(nn.Module):
|
|
1082
|
+
def __init__(self, dim_input: int, dim_outputs: list[int], device: torch.device):
|
|
1083
|
+
super().__init__()
|
|
1084
|
+
self.dim_input = dim_input
|
|
1085
|
+
self.dim_outputs = dim_outputs
|
|
1086
|
+
self.device = device
|
|
1087
|
+
|
|
1088
|
+
# when using the attention in SCP we need to project the input to have the same dim as the
|
|
1089
|
+
# context compressor so that we can compute inner products between the two
|
|
1090
|
+
self.q_projs = nn.ModuleList(
|
|
1091
|
+
[
|
|
1092
|
+
nn.Linear(
|
|
1093
|
+
self.dim_input,
|
|
1094
|
+
dim_output,
|
|
1095
|
+
bias=False,
|
|
1096
|
+
device=self.device,
|
|
1097
|
+
)
|
|
1098
|
+
for dim_output in self.dim_outputs
|
|
1099
|
+
]
|
|
1100
|
+
)
|
|
1101
|
+
|
|
1102
|
+
def forward(
|
|
1103
|
+
self,
|
|
1104
|
+
history: torch.Tensor,
|
|
1105
|
+
seq_ctxs: list[torch.Tensor],
|
|
1106
|
+
seq_ctx_masks: list[torch.Tensor],
|
|
1107
|
+
) -> list[torch.Tensor]:
|
|
1108
|
+
attn_ctxs = []
|
|
1109
|
+
batch_size, history_len, _ = history.shape
|
|
1110
|
+
|
|
1111
|
+
for seq_ctx, mask, q_proj in zip(seq_ctxs, seq_ctx_masks, self.q_projs):
|
|
1112
|
+
_, ctx_len = mask.shape
|
|
1113
|
+
mask = mask.unsqueeze(1).expand(batch_size, history_len, ctx_len)
|
|
1114
|
+
query = q_proj(history)
|
|
1115
|
+
attn_ctx = torch.nn.functional.scaled_dot_product_attention(
|
|
1116
|
+
query=query,
|
|
1117
|
+
key=seq_ctx,
|
|
1118
|
+
value=seq_ctx,
|
|
1119
|
+
attn_mask=mask,
|
|
1120
|
+
dropout_p=0.0,
|
|
1121
|
+
is_causal=False,
|
|
1122
|
+
) # (batch_size, history_len, hidden_dim)
|
|
1123
|
+
attn_ctxs.append(attn_ctx)
|
|
1124
|
+
if attn_ctxs:
|
|
1125
|
+
seq_ctxs = [torch.cat(attn_ctxs, dim=-1)]
|
|
1126
|
+
return seq_ctxs
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
class SequentialModel(nn.Module):
|
|
1130
|
+
def __init__(
|
|
1131
|
+
self,
|
|
1132
|
+
tgt_cardinalities: dict[str, int],
|
|
1133
|
+
tgt_seq_len_median: int,
|
|
1134
|
+
tgt_seq_len_max: int,
|
|
1135
|
+
ctx_cardinalities: dict[str, int],
|
|
1136
|
+
ctxseq_len_median: dict[str, int],
|
|
1137
|
+
model_size: ModelSizeOrUnits,
|
|
1138
|
+
column_order: list[str] | None,
|
|
1139
|
+
device: torch.device,
|
|
1140
|
+
with_dp: bool = False,
|
|
1141
|
+
):
|
|
1142
|
+
super().__init__()
|
|
1143
|
+
|
|
1144
|
+
self.tgt_cardinalities = tgt_cardinalities
|
|
1145
|
+
self.tgt_sub_columns = get_sub_columns_from_cardinalities(tgt_cardinalities)
|
|
1146
|
+
self.tgt_columns = get_columns_from_cardinalities(tgt_cardinalities)
|
|
1147
|
+
self.tgt_column_sub_columns = get_sub_columns_nested_from_cardinalities(tgt_cardinalities, groupby="columns")
|
|
1148
|
+
self.tgt_sub_columns_lookup = get_sub_columns_lookup(self.tgt_column_sub_columns)
|
|
1149
|
+
self.tgt_seq_len_median = tgt_seq_len_median
|
|
1150
|
+
self.tgt_last_sub_cols = [sub_cols[-1] for sub_cols in self.tgt_column_sub_columns.values()]
|
|
1151
|
+
|
|
1152
|
+
self.model_size = model_size
|
|
1153
|
+
self.column_order = column_order
|
|
1154
|
+
self.tgt_seq_len_max = tgt_seq_len_max
|
|
1155
|
+
self.device = device or torch.device("cpu")
|
|
1156
|
+
self.with_dp = with_dp
|
|
1157
|
+
|
|
1158
|
+
# context
|
|
1159
|
+
self.context_compressor = ContextCompressor(
|
|
1160
|
+
model_size=self.model_size,
|
|
1161
|
+
ctx_cardinalities=ctx_cardinalities,
|
|
1162
|
+
ctxseq_len_median=ctxseq_len_median,
|
|
1163
|
+
device=device,
|
|
1164
|
+
with_dp=with_dp,
|
|
1165
|
+
)
|
|
1166
|
+
|
|
1167
|
+
# sub column embeddings
|
|
1168
|
+
self.embedders = Embedders(
|
|
1169
|
+
model_size=self.model_size,
|
|
1170
|
+
cardinalities=self.tgt_cardinalities,
|
|
1171
|
+
device=self.device,
|
|
1172
|
+
)
|
|
1173
|
+
|
|
1174
|
+
# column embeddings
|
|
1175
|
+
self.column_embedders = ColumnEmbedders(
|
|
1176
|
+
model_size=self.model_size,
|
|
1177
|
+
cardinalities=self.tgt_cardinalities,
|
|
1178
|
+
embedding_dims=self.embedders.dims,
|
|
1179
|
+
device=self.device,
|
|
1180
|
+
)
|
|
1181
|
+
|
|
1182
|
+
# history
|
|
1183
|
+
self.history_compressor = HistoryCompressor(
|
|
1184
|
+
model_size=self.model_size,
|
|
1185
|
+
embedding_dims=self.embedders.dims,
|
|
1186
|
+
seq_len_median=self.tgt_seq_len_median,
|
|
1187
|
+
device=self.device,
|
|
1188
|
+
with_dp=with_dp,
|
|
1189
|
+
)
|
|
1190
|
+
|
|
1191
|
+
# attention module connecting sequential context(s) to history
|
|
1192
|
+
self.attention = AttentionModule(
|
|
1193
|
+
dim_input=self.history_compressor.dim_output,
|
|
1194
|
+
dim_outputs=self.context_compressor.sequential_context_compressor.dim_outputs,
|
|
1195
|
+
device=self.device,
|
|
1196
|
+
)
|
|
1197
|
+
|
|
1198
|
+
# regressors
|
|
1199
|
+
self.regressors = Regressors(
|
|
1200
|
+
model_size=self.model_size,
|
|
1201
|
+
cardinalities=self.tgt_cardinalities,
|
|
1202
|
+
context_dim=self.context_compressor.dim_output,
|
|
1203
|
+
history_dim=self.history_compressor.dim_output,
|
|
1204
|
+
embedding_dims=self.embedders.dims,
|
|
1205
|
+
column_embedding_dims=self.column_embedders.dims,
|
|
1206
|
+
device=self.device,
|
|
1207
|
+
)
|
|
1208
|
+
|
|
1209
|
+
# predictors
|
|
1210
|
+
self.predictors = Predictors(
|
|
1211
|
+
cardinalities=self.tgt_cardinalities,
|
|
1212
|
+
regressors_dims=self.regressors.dims_output,
|
|
1213
|
+
device=device,
|
|
1214
|
+
)
|
|
1215
|
+
|
|
1216
|
+
def _repeat_flat_context(self, flat_ctx: list[torch.Tensor], repetition: int) -> list[torch.Tensor]:
|
|
1217
|
+
if len(flat_ctx) > 0:
|
|
1218
|
+
flat_ctx = flat_ctx[0].unsqueeze(1)
|
|
1219
|
+
flat_ctx = flat_ctx.repeat(1, repetition, 1)
|
|
1220
|
+
flat_ctx = [flat_ctx]
|
|
1221
|
+
return flat_ctx
|
|
1222
|
+
|
|
1223
|
+
def forward(
|
|
1224
|
+
self,
|
|
1225
|
+
x,
|
|
1226
|
+
mode: Literal["trn", "gen"],
|
|
1227
|
+
batch_size: int | None = None,
|
|
1228
|
+
fixed_probs=None,
|
|
1229
|
+
fixed_values=None,
|
|
1230
|
+
temperature: float | None = None,
|
|
1231
|
+
top_p: float | None = None,
|
|
1232
|
+
history=None,
|
|
1233
|
+
history_state=None,
|
|
1234
|
+
context=None,
|
|
1235
|
+
) -> tuple[dict[str, torch.Tensor], torch.Tensor, torch.Tensor]:
|
|
1236
|
+
fixed_probs = fixed_probs or {}
|
|
1237
|
+
fixed_values = fixed_values or {}
|
|
1238
|
+
if context is None:
|
|
1239
|
+
context = self.context_compressor(x)
|
|
1240
|
+
|
|
1241
|
+
outputs = {}
|
|
1242
|
+
if mode == "trn":
|
|
1243
|
+
# forward pass through sub column embedders
|
|
1244
|
+
tgt_embeds = self.embedders(x)
|
|
1245
|
+
|
|
1246
|
+
# forward pass through column embedders
|
|
1247
|
+
tgt_col_embeds = self.column_embedders(tgt_embeds)
|
|
1248
|
+
|
|
1249
|
+
# history
|
|
1250
|
+
# time shift: remove last time step; add zeros for first time step; add randoms for all others
|
|
1251
|
+
embeddings = torch.cat(list(tgt_embeds.values()), dim=-1)
|
|
1252
|
+
history_in = embeddings[:, :-1, :]
|
|
1253
|
+
history_in = nn.ConstantPad2d((0, 0, 1, 0), 0)(history_in)
|
|
1254
|
+
history, _ = self.history_compressor(history_in)
|
|
1255
|
+
|
|
1256
|
+
flat_ctx, seq_ctx, seq_ctx_masks = context
|
|
1257
|
+
# attention with history as query
|
|
1258
|
+
seq_ctx = self.attention(history, seq_ctx, seq_ctx_masks)
|
|
1259
|
+
# repeat flat context to match the length of history
|
|
1260
|
+
flat_ctx = self._repeat_flat_context(flat_ctx, history.size(1))
|
|
1261
|
+
context_history = [torch.cat(flat_ctx + seq_ctx + [history], -1)]
|
|
1262
|
+
|
|
1263
|
+
# create batch-wise permutation mask
|
|
1264
|
+
col_embeddings = torch.cat(list(tgt_col_embeds.values()), dim=-1)
|
|
1265
|
+
col_mask = _make_permutation_mask(
|
|
1266
|
+
col_embedding_dims=self.column_embedders.dims,
|
|
1267
|
+
columns=self.tgt_columns,
|
|
1268
|
+
column_order=self.column_order,
|
|
1269
|
+
is_sequential=True,
|
|
1270
|
+
device=self.device,
|
|
1271
|
+
)
|
|
1272
|
+
|
|
1273
|
+
for sub_col, lookup in self.tgt_sub_columns_lookup.items():
|
|
1274
|
+
# mask concatenated column embeddings
|
|
1275
|
+
masked_col_embeds = [torch.mul(col_mask[lookup.col_idx, :].int(), col_embeddings)]
|
|
1276
|
+
|
|
1277
|
+
# collect previous sub column embeddings for current column
|
|
1278
|
+
prev_sub_col_embeds = [
|
|
1279
|
+
tgt_embeds[sub_col] for sub_col in self.tgt_sub_columns[lookup.sub_col_offset : lookup.sub_col_cum]
|
|
1280
|
+
]
|
|
1281
|
+
|
|
1282
|
+
# regressor
|
|
1283
|
+
regressor_in = context_history + masked_col_embeds + prev_sub_col_embeds
|
|
1284
|
+
xs = self.regressors(regressor_in, sub_col)
|
|
1285
|
+
|
|
1286
|
+
# predictor
|
|
1287
|
+
xs = self.predictors(xs, sub_col)
|
|
1288
|
+
|
|
1289
|
+
# update output
|
|
1290
|
+
outputs[sub_col] = xs
|
|
1291
|
+
|
|
1292
|
+
return outputs, {}
|
|
1293
|
+
|
|
1294
|
+
else: # mode == "gen"
|
|
1295
|
+
if history is None or history_state is None:
|
|
1296
|
+
# initialize history
|
|
1297
|
+
history_in = torch.cat(
|
|
1298
|
+
[
|
|
1299
|
+
torch.zeros(
|
|
1300
|
+
(batch_size, 1, self.embedders.dims[i]),
|
|
1301
|
+
device=self.device,
|
|
1302
|
+
)
|
|
1303
|
+
for i in range(len(self.tgt_cardinalities))
|
|
1304
|
+
],
|
|
1305
|
+
dim=-1,
|
|
1306
|
+
)
|
|
1307
|
+
# history_state is tuple of current and hidden LSTM state; see nn.LSTM.forward() for further details
|
|
1308
|
+
history, history_state = self.history_compressor(history_in)
|
|
1309
|
+
|
|
1310
|
+
flat_ctx, seq_ctx, seq_ctx_masks = context
|
|
1311
|
+
# attention between sequential context and history
|
|
1312
|
+
seq_ctx = self.attention(history, seq_ctx, seq_ctx_masks)
|
|
1313
|
+
# repeat flat context to match the length of history
|
|
1314
|
+
flat_ctx = self._repeat_flat_context(flat_ctx, history.size(1))
|
|
1315
|
+
context_history = [torch.cat(flat_ctx + seq_ctx + [history], -1)]
|
|
1316
|
+
|
|
1317
|
+
# initialize sub column embeddings
|
|
1318
|
+
tgt_embeds = self.embedders.zero_mask(batch_size, 1)
|
|
1319
|
+
|
|
1320
|
+
# initialize column embeddings
|
|
1321
|
+
tgt_col_embeds = self.column_embedders.zero_mask(batch_size, 1)
|
|
1322
|
+
|
|
1323
|
+
# concatenate column embeddings
|
|
1324
|
+
col_embeddings = torch.cat(list(tgt_col_embeds.values()), dim=-1)
|
|
1325
|
+
|
|
1326
|
+
# take sub columns in the specified generation order
|
|
1327
|
+
column_order = self.column_order or self.tgt_columns
|
|
1328
|
+
sub_column_order = [sub_col for col in column_order for sub_col in self.tgt_column_sub_columns[col]]
|
|
1329
|
+
|
|
1330
|
+
for sub_col in sub_column_order:
|
|
1331
|
+
lookup = self.tgt_sub_columns_lookup[sub_col]
|
|
1332
|
+
|
|
1333
|
+
# if sub column is fixed, skip sampling and use that value
|
|
1334
|
+
if sub_col in fixed_values:
|
|
1335
|
+
out = fixed_values[sub_col]
|
|
1336
|
+
|
|
1337
|
+
else: # sample from distribution
|
|
1338
|
+
# collect previous sub column embeddings for current column
|
|
1339
|
+
prev_sub_col_embeds = [
|
|
1340
|
+
tgt_embeds[sub_col]
|
|
1341
|
+
for sub_col in self.tgt_sub_columns[lookup.sub_col_offset : lookup.sub_col_cum]
|
|
1342
|
+
]
|
|
1343
|
+
|
|
1344
|
+
# regressor
|
|
1345
|
+
regressor_in = context_history + [col_embeddings] + prev_sub_col_embeds
|
|
1346
|
+
xs = self.regressors(regressor_in, sub_col)
|
|
1347
|
+
|
|
1348
|
+
# predictor
|
|
1349
|
+
xs = self.predictors(xs, sub_col)
|
|
1350
|
+
|
|
1351
|
+
# sample
|
|
1352
|
+
xs = nn.Softmax(dim=-1)(xs)
|
|
1353
|
+
xs = torch.squeeze(xs[:, -1:, :], -2) # take only last element
|
|
1354
|
+
out = _sample(
|
|
1355
|
+
probs=xs,
|
|
1356
|
+
temperature=temperature,
|
|
1357
|
+
top_p=top_p,
|
|
1358
|
+
fixed_probs=fixed_probs.get(sub_col),
|
|
1359
|
+
)
|
|
1360
|
+
|
|
1361
|
+
# update timestep output
|
|
1362
|
+
outputs[sub_col] = out
|
|
1363
|
+
|
|
1364
|
+
# update current sub column embedding
|
|
1365
|
+
tgt_embeds[sub_col] = self.embedders.get(sub_col)(out)
|
|
1366
|
+
|
|
1367
|
+
# update current column embedding
|
|
1368
|
+
if sub_col in self.tgt_last_sub_cols:
|
|
1369
|
+
col_sub_cols = self.tgt_column_sub_columns[lookup.col_name]
|
|
1370
|
+
col_embed_in = torch.cat([tgt_embeds[sc] for sc in col_sub_cols], dim=-1)
|
|
1371
|
+
tgt_col_embeds[lookup.col_name] = self.column_embedders.get(lookup.col_name)(col_embed_in)
|
|
1372
|
+
col_embeddings = torch.cat(list(tgt_col_embeds.values()), dim=-1)
|
|
1373
|
+
|
|
1374
|
+
# update history and hidden state
|
|
1375
|
+
history_in = torch.cat([v for v in tgt_embeds.values()], dim=-1)
|
|
1376
|
+
history, history_state = self.history_compressor(history_in, history_state=history_state)
|
|
1377
|
+
|
|
1378
|
+
# order outputs according to tgt_sub_columns
|
|
1379
|
+
outputs = {sub_col: outputs[sub_col] for sub_col in self.tgt_cardinalities}
|
|
1380
|
+
|
|
1381
|
+
# gather output
|
|
1382
|
+
return outputs, history, history_state
|