codadapt 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.
@@ -0,0 +1,75 @@
1
+ """Lossless shared native multiclass prediction and persistence."""
2
+
3
+ import numpy as np
4
+
5
+ from ._core import sigmoid
6
+ from ._multiclass_packing import pack, unpack
7
+ from ._training import predict_coded_memory_buckets
8
+
9
+
10
+ class CompactHead:
11
+ __slots__ = ("shared_state", "intercept", "levels")
12
+
13
+ def __init__(self, shared_state, intercept, levels):
14
+ self.shared_state, self.intercept, self.levels = shared_state, intercept, levels
15
+
16
+ @property
17
+ def table_size(self):
18
+ return self.shared_state.table_size
19
+
20
+ def scores(self, z):
21
+ s = self.shared_state
22
+ return predict_coded_memory_buckets(
23
+ self.levels, s.encoder.level_views_, z, self.intercept, s.table_size
24
+ )
25
+
26
+
27
+ def probabilities(logits):
28
+ p = sigmoid(logits)
29
+ total = p.sum(axis=1, keepdims=True)
30
+ if not np.isfinite(p).all() or np.any(total <= 0):
31
+ raise FloatingPointError("Invalid OVR probability mass; no silent uniform repair")
32
+ return p / total
33
+
34
+
35
+ class SharedState:
36
+ __slots__ = ("encoder", "classes", "table_size", "params")
37
+
38
+ def __init__(self, encoder, classes, table_size, params):
39
+ self.encoder, self.classes, self.table_size, self.params = (
40
+ encoder,
41
+ classes,
42
+ table_size,
43
+ params,
44
+ )
45
+
46
+
47
+ class Artifact:
48
+ __slots__ = ("shared_state", "heads")
49
+
50
+ @classmethod
51
+ def create(cls, encoder, classes, heads, params):
52
+ result = cls()
53
+ result.__setstate__(pack(encoder, classes, heads, params))
54
+ return result
55
+
56
+ def decision_function(self, x):
57
+ z = self.shared_state.encoder.transform_levels(x)
58
+ return np.column_stack([h.scores(z) for h in self.heads])
59
+
60
+ def predict_proba(self, x):
61
+ return probabilities(self.decision_function(x))
62
+
63
+ def predict(self, x):
64
+ return self.shared_state.classes[self.predict_proba(x).argmax(1)]
65
+
66
+ def __getstate__(self):
67
+ s = self.shared_state
68
+ return pack(s.encoder, s.classes, self.heads, s.params)
69
+
70
+ def __setstate__(self, wire):
71
+ encoder, classes, records, table_size, params = unpack(wire)
72
+ self.shared_state = SharedState(encoder, classes, table_size, params)
73
+ self.heads = tuple(
74
+ CompactHead(self.shared_state, intercept, levels) for intercept, levels in records
75
+ )
@@ -0,0 +1,165 @@
1
+ """Dense float64 coefficient packing; immutable metadata pooling, no compression."""
2
+
3
+ import copy
4
+
5
+ import numpy as np
6
+
7
+ from .preprocessing import BucketEncoder, _ResolutionView
8
+
9
+
10
+ def minimal_index(a):
11
+ if a.dtype.kind not in "iu" or (a.size and a.min() < 0):
12
+ return a
13
+ maximum = int(a.max()) if a.size else 0
14
+ dtype = (
15
+ np.uint8
16
+ if maximum <= 255
17
+ else np.uint16
18
+ if maximum <= 65535
19
+ else np.uint32
20
+ if maximum <= 2**32 - 1
21
+ else a.dtype
22
+ )
23
+ narrowed = a.astype(dtype)
24
+ assert np.array_equal(narrowed.astype(a.dtype), a)
25
+ return narrowed
26
+
27
+
28
+ class Pool:
29
+ def __init__(self):
30
+ self.parts, self.sizes, self.memo = {}, {}, {}
31
+
32
+ def add(self, original, indices=True):
33
+ a = minimal_index(original) if indices else original
34
+ a = np.ascontiguousarray(a)
35
+ signature = (a.dtype.str, a.shape, a.tobytes())
36
+ if signature not in self.memo:
37
+ dtype = a.dtype.str
38
+ offset = self.sizes.get(dtype, 0)
39
+ self.parts.setdefault(dtype, []).append(a.ravel().copy())
40
+ self.sizes[dtype] = offset + a.size
41
+ self.memo[signature] = ("a", dtype, offset, a.shape)
42
+ return self.memo[signature]
43
+
44
+ def buffers(self):
45
+ return {dtype: np.concatenate(arrays) for dtype, arrays in self.parts.items()}
46
+
47
+
48
+ def encode(value, pool):
49
+ if isinstance(value, np.ndarray) and value.dtype.kind != "O":
50
+ return pool.add(value)
51
+ if isinstance(value, dict):
52
+ return ("d", tuple((k, encode(v, pool)) for k, v in value.items()))
53
+ if isinstance(value, list):
54
+ return ("l", tuple(encode(v, pool) for v in value))
55
+ if isinstance(value, tuple):
56
+ return ("t", tuple(encode(v, pool) for v in value))
57
+ if isinstance(value, _ResolutionView):
58
+ return ("v", encode(vars(value), pool))
59
+ return ("p", copy.deepcopy(value))
60
+
61
+
62
+ def decode(value, buffers, memo):
63
+ tag, *rest = value
64
+ if tag == "a":
65
+ dtype, offset, shape = rest
66
+ key = (dtype, offset, tuple(shape))
67
+ if key not in memo:
68
+ length = int(np.prod(shape))
69
+ a = buffers[dtype][offset : offset + length].reshape(shape)
70
+ a.flags.writeable = False
71
+ memo[key] = a
72
+ return memo[key]
73
+ if tag == "p":
74
+ return rest[0]
75
+ if tag == "d":
76
+ return {k: decode(v, buffers, memo) for k, v in rest[0]}
77
+ if tag in ("l", "t"):
78
+ values = [decode(v, buffers, memo) for v in rest[0]]
79
+ return values if tag == "l" else tuple(values)
80
+ if tag == "v":
81
+ result = object.__new__(_ResolutionView)
82
+ result.__dict__.update(decode(rest[0], buffers, memo))
83
+ return result
84
+ raise ValueError("Unsupported artifact tag")
85
+
86
+
87
+ def pack(encoder, classes, heads, params):
88
+ pool = Pool()
89
+ enc = encode(vars(encoder), pool)
90
+ values, records, cursor = [], [], 0
91
+ sizes = {h.table_size for h in heads}
92
+ if len(sizes) != 1:
93
+ raise ValueError("Frozen native heads require a common table size")
94
+
95
+ def coefficients(a):
96
+ nonlocal cursor
97
+ if a.dtype != np.float64 or a.ndim != 1:
98
+ raise ValueError("Predictive coefficient dtype/shape must remain float64/1D")
99
+ record = (cursor, len(a))
100
+ cursor += len(a)
101
+ values.append(a.copy())
102
+ return record
103
+
104
+ for head in heads:
105
+ levels = []
106
+ for level in head.levels:
107
+ levels.append(
108
+ (
109
+ level["n_bins"],
110
+ pool.add(level["main_features"]),
111
+ tuple(coefficients(a) for a in level["main_tables"]),
112
+ tuple(pool.add(a) for a in level["groups"]),
113
+ tuple(tuple(pool.add(a) for a in group) for group in level["codes"]),
114
+ tuple(coefficients(a) for a in level["tables"]),
115
+ )
116
+ )
117
+ records.append((head.intercept, tuple(levels)))
118
+ dense = np.concatenate(values) if values else np.empty(0, dtype=np.float64)
119
+ return (
120
+ 1,
121
+ enc,
122
+ classes.copy(),
123
+ pool.buffers(),
124
+ dense,
125
+ tuple(records),
126
+ sizes.pop(),
127
+ copy.deepcopy(params),
128
+ )
129
+
130
+
131
+ def unpack(wire):
132
+ version, enc, classes, buffers, values, records, table_size, params = wire
133
+ if version != 1 or values.dtype != np.float64:
134
+ raise ValueError("Unsupported artifact schema or coefficient dtype")
135
+ for buffer in [values, *buffers.values()]:
136
+ buffer.flags.writeable = False
137
+ memo = {}
138
+ encoder = object.__new__(BucketEncoder)
139
+ encoder.__dict__.update(decode(enc, buffers, memo))
140
+
141
+ def coefficients(ref):
142
+ offset, length = ref
143
+ a = values[offset : offset + length]
144
+ a.flags.writeable = False
145
+ return a
146
+
147
+ result = []
148
+ for intercept, levels in records:
149
+ result.append(
150
+ (
151
+ intercept,
152
+ [
153
+ dict(
154
+ n_bins=n,
155
+ main_features=decode(main, buffers, memo),
156
+ main_tables=[coefficients(a) for a in main_values],
157
+ groups=[decode(a, buffers, memo) for a in groups],
158
+ codes=[[decode(a, buffers, memo) for a in group] for group in codes],
159
+ tables=[coefficients(a) for a in tables],
160
+ )
161
+ for n, main, main_values, groups, codes, tables in levels
162
+ ],
163
+ )
164
+ )
165
+ return encoder, classes, result, table_size, params
codadapt/_training.py ADDED
@@ -0,0 +1,370 @@
1
+ """Block updates; all row-dependent training state stays local to this module."""
2
+
3
+ from time import perf_counter
4
+
5
+ import numpy as np
6
+
7
+ from ._core import GAIN_RTOL, addresses, aggregate, gain, sigmoid
8
+
9
+
10
+ def data_loss(y, score, weight, binary):
11
+ loss = np.logaddexp(0.0, score) - y * score if binary else 0.5 * (y - score) ** 2
12
+ return float(np.dot(weight, loss))
13
+
14
+
15
+ def objective(model, y, score, weight, binary):
16
+ penalty = sum(float(np.dot(v, v)) for v in model.main_tables_ + model.tables_)
17
+ return data_loss(y, score, weight, binary) + 0.5 * model.l2 * penalty
18
+
19
+
20
+ def _surrogate(y, score, old, weight, binary):
21
+ if binary:
22
+ return weight / 4.0, old + 4.0 * (y - sigmoid(score))
23
+ return weight, y - (score - old)
24
+
25
+
26
+ def _selected_groups(feature_gains, n_tables):
27
+ """Select a bounded set of high-signal pairs without scanning all combinations."""
28
+ p = len(feature_gains)
29
+ if not n_tables or not p:
30
+ return []
31
+ if p == 1:
32
+ return [np.array([0], dtype=np.intp)]
33
+ order = np.argsort(-np.asarray(feature_gains), kind="stable")
34
+ groups, seen = [], set()
35
+ for offset in range(1, p):
36
+ for start in range(p):
37
+ pair = tuple(sorted((int(order[start]), int(order[(start + offset) % p]))))
38
+ if pair in seen:
39
+ continue
40
+ seen.add(pair)
41
+ groups.append(np.asarray(pair, dtype=np.intp))
42
+ if len(groups) == n_tables:
43
+ return groups
44
+ return groups
45
+
46
+
47
+ def predict_coded_memory_buckets(levels, encoders, z_levels, intercept, table_size):
48
+ """Add level memories for already encoded inputs."""
49
+ result = np.full(len(z_levels[0]), intercept, dtype=np.float64)
50
+ for level, encoder, z in zip(levels, encoders, z_levels):
51
+ main_features = level.get("main_features", range(len(level["main_tables"])))
52
+ for feature, values in zip(main_features, level["main_tables"]):
53
+ result += values[z[:, feature]]
54
+ for features, codes, values in zip(level["groups"], level["codes"], level["tables"]):
55
+ index = addresses(z, features, codes, table_size)
56
+ supported = np.ones(len(z), dtype=bool)
57
+ for feature in features:
58
+ supported &= encoder.support_[feature][z[:, feature]]
59
+ result += np.where(supported, values[index], 0.0)
60
+ return result
61
+
62
+
63
+ def _combination_count(z, features):
64
+ return int(np.unique(z[:, features], axis=0).shape[0])
65
+
66
+
67
+ def train_coded_memory(model, z_levels, y, weight, z_valid_levels, y_valid, rng, *, cache=None):
68
+ """Train the shared memory engine, optionally reusing fit-local OVR statistics.
69
+
70
+ The cache supplies the same primitives with X-only reuse across binary heads.
71
+ It is never retained in the fitted estimator or used during prediction.
72
+ """
73
+ address = addresses if cache is None else cache.address
74
+ reduce = aggregate if cache is None else cache.aggregate
75
+ surrogate = _surrogate if cache is None else cache.surrogate
76
+ combination_count = _combination_count if cache is None else cache.combination_count
77
+ binary = model._task == "classification"
78
+ n, p = z_levels[0].shape
79
+ mean = float(np.average(y, weights=weight))
80
+ if binary:
81
+ positive_mass = float(weight[y == 1].sum())
82
+ negative_mass = float(weight[y == 0].sum())
83
+ model.intercept_ = float(np.log(positive_mass) - np.log(negative_mass))
84
+ else:
85
+ model.intercept_ = mean
86
+ score = np.full(n, model.intercept_, dtype=np.float64)
87
+ model.levels_, model.level_history_, model.allocation_history_ = [], [], []
88
+ model.training_history_ = []
89
+ model.timings_.update(
90
+ score_updates=0.0,
91
+ code_search=0.0,
92
+ validation=0.0,
93
+ restore=0.0,
94
+ residual=0.0,
95
+ candidate_selection=0.0,
96
+ candidate_scoring=0.0,
97
+ candidate_application=0.0,
98
+ )
99
+ interactions_enabled = model.n_tables > 0
100
+ stopping_enabled = model.early_stopping and z_valid_levels is not None
101
+ collision_aware = True
102
+ shared_screening = True
103
+ active_feature_mask = np.ones(p, dtype=bool)
104
+ previous_feature_gains = np.full(p, np.inf, dtype=np.float64)
105
+ screening_seconds = 0.0
106
+ prefilter_discarded = 0
107
+ fully_screened = 0
108
+ remaining_budget = int(model.lookup_budget)
109
+ cumulative_memory = 0
110
+ cumulative_penalty = 0.0
111
+ best_validation = None
112
+ if z_valid_levels is not None:
113
+ initial = np.full(len(y_valid), model.intercept_, dtype=np.float64)
114
+ if binary:
115
+ best_validation = float(np.mean(np.logaddexp(0.0, initial) - y_valid * initial))
116
+ else:
117
+ best_validation = float(np.mean((y_valid - initial) ** 2))
118
+
119
+ for level_number, (z, encoder) in enumerate(zip(z_levels, model.encoders_), start=1):
120
+ if remaining_budget <= 0:
121
+ break
122
+ level_started = perf_counter()
123
+ score_before = score.copy()
124
+ penalty_before = cumulative_penalty
125
+ objective_before = data_loss(y, score, weight, binary)
126
+ objective_before += 0.5 * model.l2 * cumulative_penalty
127
+ levels_remaining = len(z_levels) - level_number + 1
128
+ level_budget = max(1, remaining_budget // levels_remaining)
129
+
130
+ screening_started = perf_counter()
131
+ candidate_features = np.flatnonzero(active_feature_mask)
132
+ h, residual_target = surrogate(y, score, np.zeros(n), weight, binary)
133
+ screened_features = candidate_features
134
+ if shared_screening and len(candidate_features):
135
+ sample_step = max(1, n // 512)
136
+ sample = np.arange(0, n, sample_step, dtype=np.intp)[:512]
137
+ sample_response = h[sample] * residual_target[sample]
138
+ cheap_scores = []
139
+ for feature in candidate_features:
140
+ size = encoder.n_buckets_[feature]
141
+ mass = np.bincount(z[sample, feature], minlength=size)
142
+ response = np.bincount(z[sample, feature], weights=sample_response, minlength=size)
143
+ cheap_scores.append(float(np.dot(response, response / (mass + model.l2))))
144
+ pool_size = min(
145
+ len(candidate_features),
146
+ max(2 * max(1, level_budget), (len(candidate_features) + 1) // 2),
147
+ )
148
+ cheap_order = np.argsort(-np.asarray(cheap_scores), kind="stable")
149
+ screened_features = candidate_features[cheap_order[:pool_size]]
150
+ discarded = candidate_features[cheap_order[pool_size:]]
151
+ active_feature_mask[discarded] = False
152
+ prefilter_discarded += len(discarded)
153
+ feature_gains = np.zeros(p, dtype=np.float64)
154
+ for feature in screened_features:
155
+ a, b = reduce(z[:, feature], h, residual_target, encoder.n_buckets_[feature])
156
+ feature_gains[feature] = gain(a, b, model.l2)
157
+ fully_screened += len(screened_features)
158
+ order = screened_features[np.argsort(-feature_gains[screened_features], kind="stable")]
159
+ maximum_feature_gain = float(feature_gains[order[0]]) if len(order) else 0.0
160
+ feature_slots = (
161
+ level_budget if not interactions_enabled else max(1, (2 * level_budget) // 3)
162
+ )
163
+ selected_features = [
164
+ int(feature)
165
+ for feature in order
166
+ if feature_gains[feature] >= 0.02 * maximum_feature_gain
167
+ ][: min(feature_slots, p)]
168
+ if not model.main_effects or maximum_feature_gain <= GAIN_RTOL:
169
+ selected_features = []
170
+ if shared_screening:
171
+ selected_set = set(selected_features)
172
+ for feature in screened_features:
173
+ weak_relative = feature_gains[feature] < 0.03 * maximum_feature_gain
174
+ weak_progress = (
175
+ np.isfinite(previous_feature_gains[feature])
176
+ and feature_gains[feature] < 0.10 * previous_feature_gains[feature]
177
+ )
178
+ if feature not in selected_set or weak_relative or weak_progress:
179
+ active_feature_mask[feature] = False
180
+ else:
181
+ previous_feature_gains[feature] = feature_gains[feature]
182
+ screening_seconds += perf_counter() - screening_started
183
+
184
+ main_tables = []
185
+ for feature in selected_features:
186
+ h, residual_target = surrogate(y, score, np.zeros(n, dtype=np.float64), weight, binary)
187
+ a, b = reduce(z[:, feature], h, residual_target, encoder.n_buckets_[feature])
188
+ values = b / (a + model.l2)
189
+ score += values[z[:, feature]]
190
+ cumulative_penalty += float(np.dot(values, values))
191
+ main_tables.append(values)
192
+
193
+ interaction_slots = min(model.n_tables, max(0, level_budget - len(selected_features)))
194
+ interaction_candidates = []
195
+ if interactions_enabled and interaction_slots:
196
+ candidate_count = min(max(2 * interaction_slots, 1), max(2 * model.n_tables, 1))
197
+ for features in _selected_groups(feature_gains, candidate_count):
198
+ group_codes = [
199
+ rng.randint(model.table_size, size=encoder.n_buckets_[feature]).astype(
200
+ np.uint16
201
+ )
202
+ for feature in features
203
+ ]
204
+ index = address(z, features, group_codes, model.table_size)
205
+ h, residual_target = surrogate(
206
+ y, score, np.zeros(n, dtype=np.float64), weight, binary
207
+ )
208
+ a, b = reduce(index, h, residual_target, model.table_size)
209
+ candidate_gain = gain(a, b, model.l2)
210
+ combinations = combination_count(z, features)
211
+ occupied = int(np.unique(index).size)
212
+ collisions = max(0, combinations - occupied)
213
+ collision_rate = collisions / combinations if combinations else 0.0
214
+ utility = (
215
+ candidate_gain / (1.0 + 3.0 * collision_rate)
216
+ if collision_aware
217
+ else candidate_gain
218
+ )
219
+ interaction_candidates.append(
220
+ (
221
+ utility,
222
+ candidate_gain,
223
+ collision_rate,
224
+ combinations,
225
+ collisions,
226
+ features,
227
+ group_codes,
228
+ )
229
+ )
230
+ interaction_candidates.sort(key=lambda item: item[0], reverse=True)
231
+
232
+ codes, groups, tables = [], [], []
233
+ collision_counts, combination_counts = [], []
234
+ top_interaction_gain = max(
235
+ (candidate[1] for candidate in interaction_candidates), default=0.0
236
+ )
237
+ for candidate in interaction_candidates:
238
+ if len(tables) >= interaction_slots:
239
+ break
240
+ _, candidate_gain, collision_rate, combinations, collisions, features, group_codes = (
241
+ candidate
242
+ )
243
+ if candidate_gain < 0.05 * top_interaction_gain:
244
+ continue
245
+ if (
246
+ collision_aware
247
+ and collision_rate > 0.55
248
+ and candidate_gain < 0.5 * top_interaction_gain
249
+ ):
250
+ continue
251
+ index = address(z, features, group_codes, model.table_size)
252
+ h, residual_target = surrogate(y, score, np.zeros(n, dtype=np.float64), weight, binary)
253
+ a, b = reduce(index, h, residual_target, model.table_size)
254
+ values = b / (a + model.l2)
255
+ score += values[index]
256
+ cumulative_penalty += float(np.dot(values, values))
257
+ groups.append(features)
258
+ codes.append(group_codes)
259
+ tables.append(values)
260
+ collision_counts.append(collisions)
261
+ combination_counts.append(combinations)
262
+
263
+ lookup_count = len(selected_features) + len(tables)
264
+ if lookup_count == 0:
265
+ break
266
+ level_memory = sum(values.nbytes for values in main_tables + tables)
267
+ level_memory += sum(code.nbytes for group in codes for code in group)
268
+ total_combinations = int(sum(combination_counts))
269
+ total_collisions = int(sum(collision_counts))
270
+ collision_rate = total_collisions / total_combinations if total_combinations else 0.0
271
+ level = {
272
+ "n_bins": int(model.resolution_bins_[level_number - 1]),
273
+ "main_features": np.asarray(selected_features, dtype=np.intp),
274
+ "main_tables": main_tables,
275
+ "groups": groups,
276
+ "codes": codes,
277
+ "tables": tables,
278
+ }
279
+ candidate_levels = model.levels_ + [level]
280
+ validation_loss = None
281
+ validation_gain = np.nan
282
+ if z_valid_levels is not None:
283
+ validation_started = perf_counter()
284
+ validation_prediction = predict_coded_memory_buckets(
285
+ candidate_levels,
286
+ model.encoders_[:level_number],
287
+ z_valid_levels[:level_number],
288
+ model.intercept_,
289
+ model.table_size,
290
+ )
291
+ if binary:
292
+ validation_loss = float(
293
+ np.mean(
294
+ np.logaddexp(0.0, validation_prediction) - y_valid * validation_prediction
295
+ )
296
+ )
297
+ else:
298
+ validation_loss = float(np.mean((y_valid - validation_prediction) ** 2))
299
+ validation_gain = float(best_validation - validation_loss)
300
+ model.timings_["validation"] += perf_counter() - validation_started
301
+
302
+ objective_value = data_loss(y, score, weight, binary)
303
+ objective_value += 0.5 * model.l2 * cumulative_penalty
304
+ gain_per_lookup = (objective_before - objective_value) / lookup_count
305
+ accepted = not stopping_enabled or validation_gain > model.tol * max(
306
+ 1.0, abs(best_validation)
307
+ )
308
+ residual = y - sigmoid(score) if binary else y - score
309
+ history = {
310
+ "level": level_number,
311
+ "n_bins": level["n_bins"],
312
+ "accepted": bool(accepted),
313
+ "active_features": selected_features,
314
+ "active_interactions": len(tables),
315
+ "lookup_count": lookup_count,
316
+ "train_loss": float(data_loss(y, score, weight, binary) / weight.sum()),
317
+ "objective": float(objective_value),
318
+ "validation_loss": validation_loss,
319
+ "validation_gain": float(validation_gain),
320
+ "gain_per_lookup": float(gain_per_lookup),
321
+ "residual_mean_abs": float(np.average(np.abs(residual), weights=weight)),
322
+ "fit_seconds": float(perf_counter() - level_started),
323
+ "added_memory_bytes": int(level_memory),
324
+ "collision_count": total_collisions,
325
+ "combination_count": total_combinations,
326
+ "collision_rate": float(collision_rate),
327
+ }
328
+ model.allocation_history_.append(history.copy())
329
+ if not accepted:
330
+ score = score_before
331
+ cumulative_penalty = penalty_before
332
+ break
333
+
334
+ model.levels_.append(level)
335
+ cumulative_memory += int(level_memory)
336
+ remaining_budget -= lookup_count
337
+ history["cumulative_memory_bytes"] = int(cumulative_memory)
338
+ model.level_history_.append(history)
339
+ model.training_history_.append(
340
+ {
341
+ "iteration": len(model.levels_),
342
+ "train_loss": history["train_loss"],
343
+ "objective": history["objective"],
344
+ "validation_loss": validation_loss,
345
+ "coordinates_examined": 0,
346
+ "accepted_moves": 0,
347
+ "candidates_evaluated": len(interaction_candidates),
348
+ }
349
+ )
350
+ if validation_loss is not None:
351
+ best_validation = validation_loss
352
+
353
+ model.main_tables_ = [values for level in model.levels_ for values in level["main_tables"]]
354
+ model.groups_ = [group for level in model.levels_ for group in level["groups"]]
355
+ model.codes_ = [codes for level in model.levels_ for codes in level["codes"]]
356
+ model.tables_ = [values for level in model.levels_ for values in level["tables"]]
357
+ model.level_memory_bytes_ = int(cumulative_memory)
358
+ model.effective_levels_ = len(model.levels_)
359
+ model.active_features_per_level_ = [len(level["main_features"]) for level in model.levels_]
360
+ model.active_interactions_ = sum(len(level["tables"]) for level in model.levels_)
361
+ model.lookup_count_per_sample_ = sum(
362
+ len(level["main_tables"]) + len(level["tables"]) for level in model.levels_
363
+ )
364
+ model.n_iter_ = len(model.levels_)
365
+ model.screening_seconds_ = float(screening_seconds)
366
+ model.prefilter_discarded_ = int(prefilter_discarded)
367
+ model.fully_screened_candidates_ = int(fully_screened)
368
+ model.feature_level_stopping_ = int(np.count_nonzero(~active_feature_mask))
369
+ model.best_iteration_ = model.n_iter_
370
+ model.accepted_moves_ = 0
codadapt/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"