morphtree 2.1.0__py3-none-win_amd64.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.
- morphtree/__init__.py +28 -0
- morphtree/__main__.py +3 -0
- morphtree/_experimental/__init__.py +1 -0
- morphtree/_experimental/admission.py +364 -0
- morphtree/_experimental/base.py +215 -0
- morphtree/_experimental/component_cache_v2.py +263 -0
- morphtree/_experimental/evolution_report.py +307 -0
- morphtree/_experimental/execution_v2.py +697 -0
- morphtree/_experimental/facade.py +289 -0
- morphtree/_experimental/integrated.py +748 -0
- morphtree/_experimental/learning.py +418 -0
- morphtree/_experimental/manifest.json +204 -0
- morphtree/_experimental/numeric_fixture.py +21 -0
- morphtree/_experimental/parameter_contract.py +38 -0
- morphtree/_experimental/postfit.py +51 -0
- morphtree/_experimental/preprocessing.py +133 -0
- morphtree/_experimental/processes.py +191 -0
- morphtree/_experimental/producer.py +542 -0
- morphtree/_experimental/product.py +156 -0
- morphtree/_experimental/product_cli.py +43 -0
- morphtree/_experimental/roundtrip_v3.py +311 -0
- morphtree/_experimental/runtime_io.py +65 -0
- morphtree/_experimental/sampling.py +26 -0
- morphtree/_experimental/support.py +281 -0
- morphtree/_experimental/target.py +59 -0
- morphtree/_experimental/training.py +53 -0
- morphtree/_experimental/worker.py +682 -0
- morphtree/_installed.py +17 -0
- morphtree/_native/__init__.py +1 -0
- morphtree/_native/bin/win_amd64/morphtree_kernels.dll +0 -0
- morphtree/_native/buffers.py +82 -0
- morphtree/_native/loader.py +39 -0
- morphtree/_native/manifest.json +29 -0
- morphtree/_native/runtime.py +48 -0
- morphtree/_native/src/histogram.c +61 -0
- morphtree/_native/src/traversal.c +51 -0
- morphtree/api.py +388 -0
- morphtree/api_compat.py +65 -0
- morphtree/backend.py +162 -0
- morphtree/booster.py +252 -0
- morphtree/cli.py +52 -0
- morphtree/config.py +262 -0
- morphtree/control.py +20 -0
- morphtree/data.py +300 -0
- morphtree/diagnostics.py +158 -0
- morphtree/estimators.py +156 -0
- morphtree/examples.py +25 -0
- morphtree/exporting.py +224 -0
- morphtree/legacy_artifacts.py +32 -0
- morphtree/metrics.py +156 -0
- morphtree/model.py +1010 -0
- morphtree/mutations.py +205 -0
- morphtree/native_lab_backend.py +79 -0
- morphtree/numpy_backend.py +180 -0
- morphtree/optuna_tuner.py +154 -0
- morphtree/persistence.py +414 -0
- morphtree/prediction_output.py +35 -0
- morphtree/prepared_bins.py +82 -0
- morphtree/public.py +106 -0
- morphtree/resource_plan.py +170 -0
- morphtree/scalable.py +781 -0
- morphtree/scalable_backend.py +74 -0
- morphtree/scalable_checkpoint.py +129 -0
- morphtree/scalable_data.py +255 -0
- morphtree/scalable_receipts.py +92 -0
- morphtree/scalable_target.py +68 -0
- morphtree/scalable_variants.py +133 -0
- morphtree/sklearn_view.py +79 -0
- morphtree/source_markers.py +25 -0
- morphtree/species.py +47 -0
- morphtree/temporary.py +65 -0
- morphtree/training_fragment.py +130 -0
- morphtree/tree.py +231 -0
- morphtree/tuning.py +150 -0
- morphtree-2.1.0.dist-info/METADATA +212 -0
- morphtree-2.1.0.dist-info/RECORD +79 -0
- morphtree-2.1.0.dist-info/WHEEL +4 -0
- morphtree-2.1.0.dist-info/entry_points.txt +2 -0
- morphtree-2.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Installed integrated consumer of explicitly supplied, authenticated models.
|
|
2
|
+
|
|
3
|
+
Packaged with relative imports; not imported as a workspace learner. SHA is a
|
|
4
|
+
caller-supplied custody identity, not a claim that a model is scientifically good.
|
|
5
|
+
"""
|
|
6
|
+
import copy
|
|
7
|
+
import hashlib
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from dataclasses import asdict, replace
|
|
11
|
+
from morphtree.config import HyperParams
|
|
12
|
+
from morphtree.persistence import read_json, LoadLimits
|
|
13
|
+
from .admission import AdmissionError, digest
|
|
14
|
+
from .runtime_io import configure, artifact_path, publish
|
|
15
|
+
from .base import Base, _payloads
|
|
16
|
+
from .facade import Control
|
|
17
|
+
from .roundtrip_v3 import decode
|
|
18
|
+
from .support import FamilyChoice
|
|
19
|
+
from .learning import mutation_admission
|
|
20
|
+
|
|
21
|
+
class MorphTree(Control):
|
|
22
|
+
"""Unified experimental API. Fresh fit needs a new qualified v2 admission.
|
|
23
|
+
|
|
24
|
+
Use load(model, sha256=..., workdir=...) for a supplied retained model.
|
|
25
|
+
Files remain declarative and bounded; no code or data are loaded implicitly.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def fit(self, X, y):
|
|
29
|
+
self.last_attempt_ = {'status': 'BLOCKED', 'physical_fits': 0, 'trials': 0, 'reason': 'COMPLETION_TRAINING_BOUNDARY_REQUIRES_QUALIFICATION_AND_NEW_OWNER_CONSENT'}
|
|
30
|
+
raise AdmissionError(self.last_attempt_)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def load(cls, path, *, sha256, workdir):
|
|
34
|
+
configure(workdir)
|
|
35
|
+
source = Path(path).resolve()
|
|
36
|
+
if not source.is_file() or not 0 < source.stat().st_size <= 32 * 1024 ** 2:
|
|
37
|
+
raise ValueError('Explicit bounded model input required')
|
|
38
|
+
with source.open('rb') as stream:raw=stream.read(32*1024**2+1)
|
|
39
|
+
if not 0<len(raw)<=32*1024**2:raise ValueError('Explicit bounded model input required')
|
|
40
|
+
data = decode(raw, sha256)
|
|
41
|
+
if type(data) is not dict:raise ValueError('Model input must be a JSON object')
|
|
42
|
+
folder = artifact_path('.inputs')
|
|
43
|
+
folder.mkdir(exist_ok=True)
|
|
44
|
+
snapshot = artifact_path(folder / (sha256 + '.json'))
|
|
45
|
+
if snapshot.exists():
|
|
46
|
+
if snapshot.read_bytes() != raw:
|
|
47
|
+
raise ValueError('Corrupt retained input snapshot')
|
|
48
|
+
else:
|
|
49
|
+
pending = artifact_path(str(snapshot) + '.pending')
|
|
50
|
+
with pending.open('xb') as out:
|
|
51
|
+
out.write(raw)
|
|
52
|
+
out.flush()
|
|
53
|
+
os.fsync(out.fileno())
|
|
54
|
+
if snapshot.exists():
|
|
55
|
+
raise FileExistsError('Concurrent input snapshot')
|
|
56
|
+
os.rename(pending, snapshot)
|
|
57
|
+
if data.get('schema') in ('morph027-native-control-v1', 'comp002-native-control-v1', 'comp003-native-control-v1', 'comp009-native-control-v1', 'product002-native-control-v1', 'product003-native-control-v1', 'product006-native-control-v1'):
|
|
58
|
+
old = Control.load(snapshot)
|
|
59
|
+
else:
|
|
60
|
+
old = Base.load(snapshot)
|
|
61
|
+
if old.report_.get('tuning') != 'DISABLED' or old.report_.get('trials'):
|
|
62
|
+
raise ValueError('Legacy input must have an explicitly untuned report; use verified variants otherwise')
|
|
63
|
+
old._selected_variant = 'pure'
|
|
64
|
+
old._variants = {'pure': {'model': old.model_, 'recipe': copy.deepcopy(old.best_recipe_), 'origin': 'PRE_FINAL_NUMERIC_SEARCH', 'evidence': 'EXPLICIT_RETAINED_WEIGHTS_NOT_NEW_FIT', 'exportable': True, 'SEARCH_primary': old.report_.get('SEARCH_primary')}}
|
|
65
|
+
for variant in old._variants.values():
|
|
66
|
+
recipe = variant['recipe']
|
|
67
|
+
expected = HyperParams(**recipe['params'])
|
|
68
|
+
for member in variant['model'].components:
|
|
69
|
+
want = expected if recipe['family'] == 'boost' else replace(expected, n_estimators=1, learning_rate=1.0, row_fraction=1.0)
|
|
70
|
+
if asdict(member.params_) != asdict(want):
|
|
71
|
+
raise ValueError('Recipe does not describe actual native parameters')
|
|
72
|
+
obj = cls(**old.get_params())
|
|
73
|
+
obj.__dict__.update(old.__dict__)
|
|
74
|
+
obj.capacity = getattr(old, 'capacity', None)
|
|
75
|
+
obj._workdir = str(Path(workdir).resolve())
|
|
76
|
+
obj._input_sha256 = sha256
|
|
77
|
+
return obj
|
|
78
|
+
|
|
79
|
+
def _io(self):
|
|
80
|
+
self._fitted()
|
|
81
|
+
configure(self._workdir)
|
|
82
|
+
|
|
83
|
+
def predict(self, X):
|
|
84
|
+
self._io()
|
|
85
|
+
return super().predict(X)
|
|
86
|
+
|
|
87
|
+
def predict_proba(self, X):
|
|
88
|
+
self._io()
|
|
89
|
+
return super().predict_proba(X)
|
|
90
|
+
|
|
91
|
+
def recipe(self):
|
|
92
|
+
self._fitted()
|
|
93
|
+
return copy.deepcopy(self.best_recipe_)
|
|
94
|
+
|
|
95
|
+
def cost_report(self):
|
|
96
|
+
self._fitted()
|
|
97
|
+
return {'current_physical_fits': 0, 'current_trials': 0, 'current_OOF': 0, 'scope': 'RETAINED_MODEL_CONSUMPTION_ONLY', 'historical_learning_cost': 'SEE_ORIGINAL_REPORT_NOT_REMEASURED', 'source_artifact_sha256': self._input_sha256, 'peak_RSS': 'UNKNOWN', 'performance_qualified': False}
|
|
98
|
+
|
|
99
|
+
def save(self, path, *, variant='all'):
|
|
100
|
+
self._io()
|
|
101
|
+
return super().save(path, variant=variant)
|
|
102
|
+
|
|
103
|
+
def save_recipe(self, path, *, variant='selected'):
|
|
104
|
+
self._io()
|
|
105
|
+
if artifact_path(path).exists():
|
|
106
|
+
raise FileExistsError('Never replace a completed recipe')
|
|
107
|
+
return super().save_recipe(path, variant=variant)
|
|
108
|
+
|
|
109
|
+
def export_python(self, path, *, variant='selected'):
|
|
110
|
+
self._io()
|
|
111
|
+
return super().export_python(path, variant=variant)
|
|
112
|
+
|
|
113
|
+
def export_bundle(self, directory, *, variants=('pure',)):
|
|
114
|
+
self._io()
|
|
115
|
+
return super().export_bundle(directory, variants=variants)
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def select_retained(cls, anchor, candidate, *, X, y, ids, role='SEARCH'):
|
|
119
|
+
"""Compare two existing families on their exact historical SEARCH.
|
|
120
|
+
|
|
121
|
+
No new labels, new split, fabricated provenance, fit, OOF or tuning.
|
|
122
|
+
This replay measures selection correctness, never policy utility.
|
|
123
|
+
"""
|
|
124
|
+
if not isinstance(anchor, cls) or not isinstance(candidate, cls):
|
|
125
|
+
raise ValueError('Explicit authenticated model objects required')
|
|
126
|
+
anchor._io()
|
|
127
|
+
candidate._fitted()
|
|
128
|
+
a, b = (anchor.recipe(), candidate.recipe())
|
|
129
|
+
if role != 'SEARCH' or anchor.task_ != candidate.task_ or a['training_identity'] != b['training_identity']:
|
|
130
|
+
raise ValueError('Same actual TRAIN/SEARCH task identity required')
|
|
131
|
+
if a['preprocessing'] != b['preprocessing'] or getattr(anchor, 'classes_', ()) != getattr(candidate, 'classes_', ()):
|
|
132
|
+
raise ValueError('Same encoding and class vocabulary required')
|
|
133
|
+
if type(ids) is not list or not ids or len(set(ids)) != len(ids) or (len(ids) != len(y)):
|
|
134
|
+
raise ValueError('Unique aligned SEARCH IDs required')
|
|
135
|
+
targets = list(y) if anchor.task_ == 'regression' else [list(anchor.classes_).index(v) for v in y]
|
|
136
|
+
search = {'role': role, 'ids': list(ids), 'X': anchor.encoder_.transform(X), 'y': targets}
|
|
137
|
+
if digest(search) != a['training_identity'].get('SEARCH'):
|
|
138
|
+
raise ValueError('Supplied rows/labels/IDs differ from frozen SEARCH')
|
|
139
|
+
models = {'anchor': anchor, 'candidate': candidate}
|
|
140
|
+
|
|
141
|
+
def packet(name, model):
|
|
142
|
+
r = model.recipe()
|
|
143
|
+
return {'id': name, 'task': model.task_, 'family': r['family'], 'recipe': r['structure'], 'learning_identity': r['training_identity'], 'params': r['params'], 'artifact_sha256': digest(_payloads(model.model_))}
|
|
144
|
+
packets = {n: packet(n, m) for n, m in models.items()}
|
|
145
|
+
events = []
|
|
146
|
+
|
|
147
|
+
def verify(p, selection):
|
|
148
|
+
model = models[p['id']]
|
|
149
|
+
if p != packets[p['id']] or selection != search or digest(_payloads(model.model_)) != p['artifact_sha256']:
|
|
150
|
+
raise ValueError('Changed verified candidate/SEARCH')
|
|
151
|
+
return model.model_.probabilities_or_values(selection['X'])
|
|
152
|
+
chooser = FamilyChoice(packets['anchor'], search, checkpoint=events.append, verify=verify)
|
|
153
|
+
result = chooser.run(packets['candidate'])
|
|
154
|
+
chosen = models[result['packet']['id']]
|
|
155
|
+
decision = {'schema': 'morphtree-completion-retained-selection-v1', 'selected': result['packet']['id'], 'anchor_loss': result['anchor_loss'], 'selected_loss': result['loss'], 'events': events, 'decision': 'ACCEPT_OTHER_FAMILY' if chosen is candidate else 'RETAIN_ANCHOR', 'morphing': mutation_admission('auto'), 'structure_frozen': chosen.recipe(), 'physical_fits': 0, 'trials': 0, 'GUARD': 0, 'evidence': 'RETAINED_REPLAY_NOT_TRAINED_ADAPTIVE_UTILITY'}
|
|
156
|
+
return (chosen, decision)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Inference-only CLI for the integrated experimental build."""
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from .product import MorphTree
|
|
6
|
+
from .target import _NamedRows
|
|
7
|
+
from .roundtrip_v3 import decode
|
|
8
|
+
import hashlib
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main(argv=None):
|
|
12
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
13
|
+
parser.add_argument('command', choices=('inspect', 'predict', 'export'))
|
|
14
|
+
parser.add_argument('--model', required=True)
|
|
15
|
+
parser.add_argument('--sha256', required=True)
|
|
16
|
+
parser.add_argument('--workdir', required=True)
|
|
17
|
+
parser.add_argument('--input')
|
|
18
|
+
parser.add_argument('--output')
|
|
19
|
+
args = parser.parse_args(argv)
|
|
20
|
+
try:
|
|
21
|
+
model = MorphTree.load(args.model, sha256=args.sha256, workdir=args.workdir)
|
|
22
|
+
if args.command == 'inspect':
|
|
23
|
+
value = {'task': model.task_, 'family': model.best_family_,
|
|
24
|
+
'recipe': model.recipe(), 'cost': model.cost_report()}
|
|
25
|
+
elif args.command == 'predict':
|
|
26
|
+
if not args.input: raise ValueError('--input JSON rows required')
|
|
27
|
+
path = Path(args.input)
|
|
28
|
+
if not 0 < path.stat().st_size <= 16*1024**2: raise ValueError('Bounded rows required')
|
|
29
|
+
raw = path.read_bytes(); rows = decode(raw, hashlib.sha256(raw).hexdigest())
|
|
30
|
+
if type(rows) is dict and set(rows) == {'columns', 'rows'}:
|
|
31
|
+
rows = _NamedRows(rows['rows'], rows['columns'])
|
|
32
|
+
value = {'predictions': model.predict(rows)}
|
|
33
|
+
if model.task_ != 'regression': value['probabilities'] = model.predict_proba(rows)
|
|
34
|
+
else:
|
|
35
|
+
if not args.output: raise ValueError('--output required')
|
|
36
|
+
model.export_python(args.output); value = {'standalone': args.output, 'new_fits': 0}
|
|
37
|
+
print(json.dumps(value, ensure_ascii=False, allow_nan=False))
|
|
38
|
+
except (ValueError, OSError, RuntimeError) as exc:
|
|
39
|
+
parser.exit(2, type(exc).__name__+': '+str(exc)+'\n')
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == '__main__':
|
|
43
|
+
main()
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""Versioned research-only roundtrip qualification, not a loader or fit permit.
|
|
2
|
+
|
|
3
|
+
Only the documented native report projections may differ. Every other byte-
|
|
4
|
+
decoded state path is compared. A successful state comparison alone cannot PASS:
|
|
5
|
+
an externally pinned, pre-save behavior witness and verified installed runtime
|
|
6
|
+
are required. Trust comes from the caller's qualified dossier, not this report.
|
|
7
|
+
"""
|
|
8
|
+
import copy
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import math
|
|
12
|
+
POLICY = 'PRODUCT002_EXPLICIT_THREE_TASK_GROWTH_ENVELOPE_V3'
|
|
13
|
+
NUMERIC = ('base_search_loss', 'base_guard_loss', 'structure_search_loss', 'structure_guard_loss', 'final_search_loss', 'final_guard_loss', 'structural_gain', 'tuning_gain')
|
|
14
|
+
FIELDS = {'active_parameters': ('B', 'list', 'algorithm.active_parameters'), 'backend': ('C', 'str', 'external installed-runtime dossier; pure/python-stdlib'), 'base_genome': ('C', 'dict', 'historical starting recipe; Base-only scope'), 'base_parameters': ('C', 'dict', 'historical starting parameters; Base-only scope'), 'config': ('C', 'dict', 'external protocol/configuration, never restored wholesale'), 'coverage': ('D', 'dict', 'discovery diagnostics, not a completion receipt'), 'error_profiles': ('D', 'list', 'diagnostic history, empty in this scope'), 'feature_schema': ('B', 'dict', 'top-level n_features/feature_names'), 'final_fingerprint': ('B', 'str', 'algorithm.structural_fingerprint'), 'final_genome': ('B', 'dict', 'algorithm.genome'), 'final_parameters': ('B', 'dict', 'algorithm.parameters'), 'freeze_reason': ('D', 'str', 'diagnostic stop explanation'), 'metrics_scope': ('C', 'str', 'TRAIN-only metric provenance, not GUARD permission'), 'morph_history': ('D', 'list', 'diagnostic history, empty in this scope'), 'plateau_reached': ('D', 'bool', 'diagnostic outcome, not scientific convergence'), 'plateau_scope': ('D', 'str', 'diagnostic scope'), 'python': ('C', 'str', 'historical interpreter; current interpreter externally pinned'), 'refit_full': ('C', 'bool', 'external learning recipe'), 'spec_version': ('B', 'int', 'top-level spec_version'), 'split': ('C', 'dict', 'TRAIN metric partition provenance, not final GUARD'), 'stop_reason': ('D', 'str', 'informational only, never COMPLETE authority'), 'structural_sampling': ('C', 'dict', 'historical TRAIN sampling provenance'), 'timings_seconds': ('D', 'dict', 'operational diagnostics, not performance evidence'), 'tuning': ('C', 'dict', 'historical tuning state, disabled in this scope'), 'version': ('B', 'str', 'top-level producer_version')}
|
|
15
|
+
|
|
16
|
+
class Unknown(ValueError):
|
|
17
|
+
"""Known-safe equivalence policy does not cover this material."""
|
|
18
|
+
|
|
19
|
+
def digest(v):
|
|
20
|
+
return hashlib.sha256(json.dumps(v, sort_keys=True, separators=(',', ':'), ensure_ascii=False, allow_nan=False).encode('utf8')).hexdigest()
|
|
21
|
+
|
|
22
|
+
def decode(b, expected):
|
|
23
|
+
if type(b) is not bytes or not 0 < len(b) <= 32 * 1024 ** 2:
|
|
24
|
+
raise ValueError('Bounded immutable bytes required')
|
|
25
|
+
if hashlib.sha256(b).hexdigest() != expected:
|
|
26
|
+
raise ValueError('Artifact SHA mismatch')
|
|
27
|
+
|
|
28
|
+
def pairs(items):
|
|
29
|
+
out = {}
|
|
30
|
+
for k, v in items:
|
|
31
|
+
if k in out:
|
|
32
|
+
raise ValueError('Duplicate JSON key')
|
|
33
|
+
out[k] = v
|
|
34
|
+
return out
|
|
35
|
+
|
|
36
|
+
def bad(_):
|
|
37
|
+
raise ValueError('Nonfinite JSON')
|
|
38
|
+
v = json.loads(b.decode('utf8'), object_pairs_hook=pairs, parse_constant=bad)
|
|
39
|
+
|
|
40
|
+
def bounded(x, depth=0):
|
|
41
|
+
if depth > 64:
|
|
42
|
+
raise ValueError('JSON depth limit')
|
|
43
|
+
if isinstance(x, dict):
|
|
44
|
+
for k, y in x.items():
|
|
45
|
+
if type(k) is not str:
|
|
46
|
+
raise ValueError('String keys required')
|
|
47
|
+
bounded(y, depth + 1)
|
|
48
|
+
elif isinstance(x, list):
|
|
49
|
+
for y in x:
|
|
50
|
+
bounded(y, depth + 1)
|
|
51
|
+
elif type(x) is float and (not math.isfinite(x)):
|
|
52
|
+
raise ValueError('Nonfinite number')
|
|
53
|
+
bounded(v)
|
|
54
|
+
digest(v)
|
|
55
|
+
return v
|
|
56
|
+
|
|
57
|
+
def differences(a, b, path=''):
|
|
58
|
+
if type(a) is not type(b):
|
|
59
|
+
return [{'path': path, 'before': a, 'after': b}]
|
|
60
|
+
if isinstance(a, dict):
|
|
61
|
+
out = []
|
|
62
|
+
for k in sorted(set(a) | set(b)):
|
|
63
|
+
p = path + '/' + k.replace('~', '~0').replace('/', '~1')
|
|
64
|
+
if k not in a or k not in b:
|
|
65
|
+
out.append({'path': p, 'before': a.get(k, 'ABSENT'), 'after': b.get(k, 'ABSENT')})
|
|
66
|
+
else:
|
|
67
|
+
out.extend(differences(a[k], b[k], p))
|
|
68
|
+
return out
|
|
69
|
+
if isinstance(a, list):
|
|
70
|
+
if len(a) != len(b):
|
|
71
|
+
return [{'path': path, 'before': a, 'after': b}]
|
|
72
|
+
return [d for i, (x, y) in enumerate(zip(a, b)) for d in differences(x, y, path + '/' + str(i))]
|
|
73
|
+
return [] if a == b else [{'path': path, 'before': a, 'after': b}]
|
|
74
|
+
|
|
75
|
+
def _finite(v):
|
|
76
|
+
return type(v) in (int, float) and math.isfinite(v)
|
|
77
|
+
|
|
78
|
+
def _binary_report(r, native):
|
|
79
|
+
from morphtree.public import imported_report, PUBLIC_CONFIG_FIELDS
|
|
80
|
+
from morphtree.config import MorphTreeConfig
|
|
81
|
+
if type(r) is not dict:
|
|
82
|
+
raise ValueError('Report object required')
|
|
83
|
+
unknown = set(r) - set(NUMERIC) - set(FIELDS)
|
|
84
|
+
if unknown:
|
|
85
|
+
raise Unknown('Unclassified binary report fields: ' + repr(sorted(unknown)))
|
|
86
|
+
types = {'str': str, 'dict': dict, 'list': list, 'bool': bool, 'int': int}
|
|
87
|
+
for k in NUMERIC:
|
|
88
|
+
if k in r and (not _finite(r[k])):
|
|
89
|
+
raise ValueError('Nonfinite/invalid metric ' + k)
|
|
90
|
+
for k, (_, typ, _) in FIELDS.items():
|
|
91
|
+
if k in r and type(r[k]) is not types[typ]:
|
|
92
|
+
raise ValueError('Report type ' + k)
|
|
93
|
+
a = native['algorithm']
|
|
94
|
+
bindings = {'active_parameters': a['active_parameters'], 'final_genome': a['genome'], 'final_parameters': a['parameters'], 'final_fingerprint': a['structural_fingerprint'], 'spec_version': native['spec_version'], 'version': native['producer_version'], 'refit_full': a['learning_recipe']['refit_full'], 'feature_schema': {'count': native['n_features'], 'names': native['feature_names']}}
|
|
95
|
+
for k, v in bindings.items():
|
|
96
|
+
if k in r and r[k] != v:
|
|
97
|
+
raise ValueError('Contradictory report/authoritative binding ' + k)
|
|
98
|
+
for k in ('morph_history', 'error_profiles'):
|
|
99
|
+
if k in r and r[k] != []:
|
|
100
|
+
raise Unknown('Nonempty unqualified ' + k)
|
|
101
|
+
for k, target in (('base_genome', 'genome'), ('base_parameters', 'parameters')):
|
|
102
|
+
if k in r and r[k] != a[target]:
|
|
103
|
+
raise Unknown('Non-Base historical ' + k)
|
|
104
|
+
if 'backend' in r and r['backend'] != 'python-stdlib':
|
|
105
|
+
raise ValueError('Backend report mismatch')
|
|
106
|
+
if 'config' in r:
|
|
107
|
+
c = r['config']
|
|
108
|
+
if not set(c) <= set(PUBLIC_CONFIG_FIELDS):
|
|
109
|
+
raise Unknown('Unexpected config fields')
|
|
110
|
+
if c.get('enable_morphing', False) or c.get('enable_tuning', False) or c.get('memory_enabled', False):
|
|
111
|
+
raise Unknown('Non-Base config outside policy')
|
|
112
|
+
for k, v in a['learning_recipe'].items():
|
|
113
|
+
if k in c and c[k] != v:
|
|
114
|
+
raise ValueError('Report/learning recipe mismatch ' + k)
|
|
115
|
+
if any((type(v) not in (bool, int, float, str, type(None)) for v in c.values())):
|
|
116
|
+
raise ValueError('Non-scalar operational config')
|
|
117
|
+
MorphTreeConfig(**c)
|
|
118
|
+
schemas = {'coverage': {'local_search_complete', 'scope', 'evaluated_neighbors', 'neighborhood_size', 'reason'}, 'split': {'guard', 'kind', 'refit_rows', 'sealed', 'search', 'train'}, 'structural_sampling': {'policy_active', 'row_fraction', 'sampled_rows', 'scope', 'train_rows'}, 'timings_seconds': {'base', 'discovery', 'refit', 'total_fit', 'tuning'}, 'tuning': {'accepted', 'attempted', 'completed', 'frozen_fingerprint', 'reason', 'trials'}}
|
|
119
|
+
for k, keys in schemas.items():
|
|
120
|
+
if k in r and (not set(r[k]) <= keys):
|
|
121
|
+
raise Unknown('Unclassified nested report ' + k)
|
|
122
|
+
if r.get('coverage', {}) != {}:
|
|
123
|
+
raise Unknown('Nonempty discovery coverage outside Base scope')
|
|
124
|
+
if 'split' in r:
|
|
125
|
+
s = r['split']
|
|
126
|
+
if set(s) != schemas['split'] or any((type(s[k]) is not int or s[k] < 0 for k in ('guard', 'refit_rows', 'search', 'train'))):
|
|
127
|
+
raise ValueError('Split diagnostic types')
|
|
128
|
+
if s['kind'] != 'baseline_no_selection_metrics_are_training_only' or s['sealed'] != 'not accepted by fit; evaluate externally' or len({s[k] for k in ('guard', 'search', 'refit_rows', 'train')}) != 1:
|
|
129
|
+
raise Unknown('Unsupported split provenance')
|
|
130
|
+
for k in ('metrics_scope', 'freeze_reason', 'plateau_scope', 'stop_reason', 'python'):
|
|
131
|
+
if k in r and len(r[k]) > 4096:
|
|
132
|
+
raise ValueError('Bounded informational annotation')
|
|
133
|
+
if 'tuning' in r:
|
|
134
|
+
t = r['tuning']
|
|
135
|
+
if set(t) != schemas['tuning'] or type(t['reason']) is not str or len(t['reason']) > 4096:
|
|
136
|
+
raise ValueError('Exact typed inactive tuning report')
|
|
137
|
+
if any((t.get(k, False) is not False for k in ('accepted', 'attempted', 'completed'))) or t.get('trials', []) != []:
|
|
138
|
+
raise Unknown('Active tuning report outside policy')
|
|
139
|
+
if 'frozen_fingerprint' in t and t['frozen_fingerprint'] != a['structural_fingerprint']:
|
|
140
|
+
raise ValueError('Tuning fingerprint mismatch')
|
|
141
|
+
if 'timings_seconds' in r and any((not _finite(v) or v < 0 for v in r['timings_seconds'].values())):
|
|
142
|
+
raise ValueError('Invalid diagnostic times')
|
|
143
|
+
if 'structural_sampling' in r:
|
|
144
|
+
s = r['structural_sampling']
|
|
145
|
+
if set(s) != schemas['structural_sampling'] or type(s['scope']) is not str or len(s['scope']) > 4096 or (not _finite(s['row_fraction'])):
|
|
146
|
+
raise ValueError('Exact typed structural sampling')
|
|
147
|
+
if any((type(s[k]) is not int or s[k] < 1 for k in ('sampled_rows', 'train_rows'))) or s['sampled_rows'] != s['train_rows']:
|
|
148
|
+
raise ValueError('Unsampled positive integer TRAIN counts required')
|
|
149
|
+
if 'split' in r and s['train_rows'] != r['split']['train']:
|
|
150
|
+
raise ValueError('Inconsistent TRAIN report counts')
|
|
151
|
+
if s.get('policy_active', False) is not False or s.get('row_fraction', 1) != 1:
|
|
152
|
+
raise Unknown('Sampled structure report outside scope')
|
|
153
|
+
return imported_report(r)
|
|
154
|
+
|
|
155
|
+
def report_projection(r, native, task):
|
|
156
|
+
if task == 'binary':
|
|
157
|
+
return _binary_report(r, native)
|
|
158
|
+
from morphtree.public import imported_multiclass_report, imported_regression_report
|
|
159
|
+
allowed = {'task', 'imported', 'train_metrics', 'validation_metrics'}
|
|
160
|
+
if task == 'regression':
|
|
161
|
+
allowed.add('objective')
|
|
162
|
+
if type(r) is not dict:
|
|
163
|
+
raise ValueError('Report object required')
|
|
164
|
+
if set(r) - allowed:
|
|
165
|
+
raise Unknown('Unclassified task report fields')
|
|
166
|
+
if r.get('task') != task or r.get('imported') is not True:
|
|
167
|
+
raise Unknown('Only normalized retained task report qualified')
|
|
168
|
+
if task == 'regression' and r.get('objective') != 'squared_error':
|
|
169
|
+
raise ValueError('Objective')
|
|
170
|
+
metrics = {'rmse', 'mae', 'r2'} if task == 'regression' else {'logloss', 'accuracy', 'macro_f1'}
|
|
171
|
+
for group in ('train_metrics', 'validation_metrics'):
|
|
172
|
+
if group not in r:
|
|
173
|
+
continue
|
|
174
|
+
if type(r[group]) is not dict or set(r[group]) - metrics:
|
|
175
|
+
raise Unknown('Unknown metric field')
|
|
176
|
+
for k, v in r[group].items():
|
|
177
|
+
if not _finite(v) or (k != 'r2' and v < 0) or (k in ('accuracy', 'macro_f1') and v > 1):
|
|
178
|
+
raise ValueError('Metric domain')
|
|
179
|
+
return (imported_regression_report if task == 'regression' else imported_multiclass_report)(r)
|
|
180
|
+
|
|
181
|
+
def canonical_state(payload):
|
|
182
|
+
if type(payload) is not dict or set(payload) != {'schema', 'constructor', 'recipe', 'classes', 'report', 'native_payloads'}:
|
|
183
|
+
raise ValueError('Exact M026 envelope required')
|
|
184
|
+
if payload['schema'] not in ('morph026-native-estimator-v1', 'comp002-native-estimator-v1', 'comp003-native-estimator-v1', 'comp009-native-estimator-v1', 'product002-native-estimator-v1', 'product003-native-estimator-v1', 'product006-native-estimator-v1'):
|
|
185
|
+
raise ValueError('Unsupported envelope')
|
|
186
|
+
if type(payload['constructor']) is not dict or set(payload['constructor']) != {'task', 'families', 'morphing', 'tuning', 'preset', 'metric', 'time_budget', 'n_trials', 'n_jobs', 'random_state', 'verbose'}:
|
|
187
|
+
raise Unknown('Unknown constructor schema')
|
|
188
|
+
recipe = payload['recipe']
|
|
189
|
+
if type(recipe) is not dict or set(recipe) != {'schema', 'task', 'family', 'structure', 'params', 'seed', 'training_identity', 'preprocessing', 'mutations'} or recipe['schema'] != {'morph026-native-estimator-v1': 'morph026-frozen-form-v1', 'comp002-native-estimator-v1': 'comp002-frozen-form-v1', 'comp009-native-estimator-v1': 'comp009-frozen-form-v1', 'comp003-native-estimator-v1': 'comp003-frozen-form-v1', 'product002-native-estimator-v1': 'product002-frozen-form-v1', 'product003-native-estimator-v1': 'product003-frozen-form-v1', 'product006-native-estimator-v1': 'product006-frozen-form-v1'}[payload['schema']]:
|
|
190
|
+
raise Unknown('Unknown frozen recipe schema')
|
|
191
|
+
if type(recipe['structure']) is not dict or set(recipe['structure']) != ({'genome', 'classes', 'members', 'encoder', 'lineage'} if payload['schema'] in ('comp003-native-estimator-v1', 'comp009-native-estimator-v1', 'product002-native-estimator-v1', 'product003-native-estimator-v1', 'product006-native-estimator-v1') else {'genome', 'classes', 'members', 'encoder'}) or recipe['mutations'] != []:
|
|
192
|
+
raise Unknown('Unknown structural recipe')
|
|
193
|
+
outer_fields = {'GUARD_used', 'SEARCH_primary', 'available_families', 'best_family', 'classes', 'cost_scope', 'descendant_cpu', 'events', 'final_model_role', 'limitations', 'metric', 'morphing', 'optional_dependencies', 'peak_RSS', 'process_cpu_seconds', 'refit_performed', 'schema', 'seed', 'selection', 'stop_reason', 'task', 'trials', 'tried', 'tuning', 'untried', 'wall_seconds', 'worker_admission', 'variants', 'exported_variant'}
|
|
194
|
+
if type(payload['report']) is not dict or set(payload['report']) - outer_fields:
|
|
195
|
+
raise Unknown('Unclassified outer report fields')
|
|
196
|
+
task = payload['recipe']['task']
|
|
197
|
+
expected = {'binary': 'MorphTreeModel', 'multiclass': 'MorphTreeMulticlassModel', 'regression': 'MorphTreeRegressionModel'}
|
|
198
|
+
if task not in expected:
|
|
199
|
+
raise ValueError('Task')
|
|
200
|
+
p = copy.deepcopy(payload)
|
|
201
|
+
if type(p['native_payloads']) is not list or not p['native_payloads']:
|
|
202
|
+
raise ValueError('Native members')
|
|
203
|
+
for n in p['native_payloads']:
|
|
204
|
+
if n.get('format') not in ((expected[task], {'regression': 'MorphTreeRegressionGrowthV2', 'multiclass': 'MorphTreeMulticlassGrowthV2'}.get(task)) if p['schema'] in ('product002-native-estimator-v1', 'product003-native-estimator-v1', 'product006-native-estimator-v1') else (expected[task],)) or n.get('spec_version') != 1 or n.get('producer_version') != '0.6.0.dev0':
|
|
205
|
+
raise ValueError('Native format/version')
|
|
206
|
+
report_projection(n['report'], n, task)
|
|
207
|
+
del n['report']
|
|
208
|
+
return p
|
|
209
|
+
|
|
210
|
+
def _safe_native_schema(payload):
|
|
211
|
+
"""Use byte-exact production validators, without fitting or inference.
|
|
212
|
+
|
|
213
|
+
Needed even when both sides contain the same unexpected field. Equality
|
|
214
|
+
alone cannot establish schema validity. No quarantine caller uses this API.
|
|
215
|
+
"""
|
|
216
|
+
from morphtree.model import MorphTreeClassifier, MorphTreeRegressor, MorphTreeMulticlassClassifier
|
|
217
|
+
classes = {'binary': MorphTreeClassifier, 'regression': MorphTreeRegressor, 'multiclass': MorphTreeMulticlassClassifier}
|
|
218
|
+
for native in payload['native_payloads']:
|
|
219
|
+
classes[payload['recipe']['task']]._from_payload(copy.deepcopy(native))
|
|
220
|
+
|
|
221
|
+
def _behavior(w, task, classes, nrows):
|
|
222
|
+
if set(w) != ({'prediction', 'probabilities'} if task != 'regression' else {'prediction'}):
|
|
223
|
+
raise ValueError('Behavior witness schema')
|
|
224
|
+
p = w['prediction']
|
|
225
|
+
if type(p) is not list or len(p) != nrows:
|
|
226
|
+
raise ValueError('Prediction alignment')
|
|
227
|
+
if task == 'regression':
|
|
228
|
+
if not all((_finite(v) for v in p)):
|
|
229
|
+
raise ValueError('Nonfinite predictions')
|
|
230
|
+
return
|
|
231
|
+
probs = w['probabilities']
|
|
232
|
+
if len(probs) != nrows or any((v not in classes for v in p)):
|
|
233
|
+
raise ValueError('Class alignment')
|
|
234
|
+
for y, row in zip(p, probs):
|
|
235
|
+
if type(row) is not list or len(row) != len(classes) or (not all((_finite(v) and 0 <= v <= 1 for v in row))):
|
|
236
|
+
raise ValueError('Probability domain')
|
|
237
|
+
if abs(math.fsum(row) - 1) > 1e-12:
|
|
238
|
+
raise ValueError('Probability normalization')
|
|
239
|
+
if classes[max(range(len(row)), key=row.__getitem__)] != y:
|
|
240
|
+
raise ValueError('Class decision rule')
|
|
241
|
+
|
|
242
|
+
def compare(before_bytes, after_bytes, *, trust, witness):
|
|
243
|
+
"""No IO, fitting, unpickling, eval or generic annotation restoration.
|
|
244
|
+
|
|
245
|
+
trust must be made by the qualified caller from verified runtime/dossier,
|
|
246
|
+
model bytes, immutable query and witness persisted before serialization.
|
|
247
|
+
This API is a cooperative verifier, not protection against a malicious caller
|
|
248
|
+
that can rewrite its own trust dossier or execute arbitrary Python in process.
|
|
249
|
+
"""
|
|
250
|
+
result = {'policy': POLICY, 'status': 'INVALID', 'A': 'UNKNOWN', 'B': 'UNKNOWN', 'C': 'UNKNOWN', 'differences': [], 'accepted_differences': [], 'new_fits': 0, 'new_trials': 0}
|
|
251
|
+
try:
|
|
252
|
+
required = {'runtime', 'actual_runtime', 'before_sha256', 'after_sha256', 'witness_sha256', 'query_sha256', 'source_sha256', 'recipe_sha256', 'loader_sha256', 'admission'}
|
|
253
|
+
if set(trust) != required:
|
|
254
|
+
raise ValueError('Exact externally pinned trust schema')
|
|
255
|
+
if trust['admission'] not in ('QUALIFIED_RETAINED_ZERO_FIT', 'QUALIFIED_COMP003_SOFTWARE', 'QUALIFIED_PRODUCT002_SOFTWARE', 'QUALIFIED_COMP003_LIVE_COMPONENTS'):
|
|
256
|
+
raise Unknown('Quarantine or missing exact admission')
|
|
257
|
+
if trust['runtime'] != trust['actual_runtime'] or trust['runtime'].get('backend') != 'pure':
|
|
258
|
+
raise ValueError('Runtime identity changed')
|
|
259
|
+
a = decode(before_bytes, trust['before_sha256'])
|
|
260
|
+
b = decode(after_bytes, trust['after_sha256'])
|
|
261
|
+
result['differences'] = differences(a, b)
|
|
262
|
+
ca = canonical_state(a)
|
|
263
|
+
cb = canonical_state(b)
|
|
264
|
+
_safe_native_schema(a)
|
|
265
|
+
_safe_native_schema(b)
|
|
266
|
+
if ca != cb:
|
|
267
|
+
raise ValueError('Critical predictive/identity state differs')
|
|
268
|
+
if digest(a['recipe']) != trust['recipe_sha256']:
|
|
269
|
+
raise ValueError('Recipe identity')
|
|
270
|
+
task = a['recipe']['task']
|
|
271
|
+
for i, (x, y) in enumerate(zip(a['native_payloads'], b['native_payloads'])):
|
|
272
|
+
projection = report_projection(x['report'], x, task)
|
|
273
|
+
report_projection(y['report'], y, task)
|
|
274
|
+
if y['report'] != projection:
|
|
275
|
+
raise ValueError('Restored report not exact safe projection')
|
|
276
|
+
for d in differences(x['report'], y['report'], f'/native_payloads/{i}/report'):
|
|
277
|
+
key = d['path'].rsplit('/', 1)[-1]
|
|
278
|
+
if key not in FIELDS:
|
|
279
|
+
raise Unknown('Unclassified report difference')
|
|
280
|
+
result['accepted_differences'].append({**d, 'category': FIELDS[key][0], 'reason': 'Original safe-loader allowlist deliberately drops this typed annotation', 'authority': FIELDS[key][2]})
|
|
281
|
+
result.update(B='PASS', C='PASS')
|
|
282
|
+
if witness is None:
|
|
283
|
+
raise Unknown('No actual pre-save witness')
|
|
284
|
+
if digest(witness) != trust['witness_sha256']:
|
|
285
|
+
raise ValueError('Witness SHA')
|
|
286
|
+
keys = {'schema', 'runtime', 'source_sha256', 'recipe_sha256', 'query', 'loader_sha256', 'before_state_sha256', 'after_state_sha256', 'before', 'after', 'events'}
|
|
287
|
+
if set(witness) != keys or witness['schema'] != 'morph032-actual-behavior-v1':
|
|
288
|
+
raise ValueError('Witness schema')
|
|
289
|
+
for k in ('runtime', 'source_sha256', 'recipe_sha256', 'loader_sha256'):
|
|
290
|
+
if witness[k] != trust[k]:
|
|
291
|
+
raise ValueError('Witness binding ' + k)
|
|
292
|
+
if digest(witness['query']) != trust['query_sha256']:
|
|
293
|
+
raise ValueError('Query identity')
|
|
294
|
+
if witness['before_state_sha256'] != digest(a) or witness['after_state_sha256'] != digest(b):
|
|
295
|
+
raise ValueError('Witness state binding')
|
|
296
|
+
ev = witness['events']
|
|
297
|
+
if set(ev) != {'captured_before_save', 'saved', 'safe_loaded', 'captured_after_load'} or not all((type(v) is int for v in ev.values())):
|
|
298
|
+
raise ValueError('Witness event schema')
|
|
299
|
+
if not ev['captured_before_save'] < ev['saved'] < ev['safe_loaded'] < ev['captured_after_load']:
|
|
300
|
+
raise ValueError('Witness acquired too late or invalid timeline')
|
|
301
|
+
n = len(witness['query']['rows'])
|
|
302
|
+
for v in ('before', 'after'):
|
|
303
|
+
_behavior(witness[v], task, a['classes'], n)
|
|
304
|
+
if witness['before'] != witness['after']:
|
|
305
|
+
raise ValueError('Actual predictions/probabilities differ')
|
|
306
|
+
result.update(status='PASS', A='PASS', reason='Exact math, actual behavior, identity and explicit safe projection')
|
|
307
|
+
except Unknown as e:
|
|
308
|
+
result.update(status='UNQUALIFIED', reason=str(e))
|
|
309
|
+
except (KeyError, TypeError, ValueError, IndexError, RecursionError, OverflowError) as e:
|
|
310
|
+
result.update(status='REJECTED', reason=type(e).__name__ + ': ' + str(e))
|
|
311
|
+
return result
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Installed IO glue, not a learner. Copied only into private MORPH030 wheels."""
|
|
2
|
+
import copy
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
from contextvars import ContextVar
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from .admission import CURRENT,AdmissionError,digest,sha,safe_path
|
|
9
|
+
from ..temporary import directory as bounded_directory
|
|
10
|
+
|
|
11
|
+
WORKDIR=ContextVar('morph030_workdir',default=None)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def configure(workdir):
|
|
15
|
+
p=Path(workdir).resolve()
|
|
16
|
+
if not p.is_dir():raise ValueError('Explicit existing working directory required')
|
|
17
|
+
safe_path(p,p);WORKDIR.set(p)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def artifact_path(value):
|
|
21
|
+
root=WORKDIR.get()
|
|
22
|
+
if root is None:raise ValueError('Call configure(workdir) before explicit IO')
|
|
23
|
+
return safe_path(root,value)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def configure_temp():
|
|
27
|
+
p=artifact_path('.temporary');p.mkdir(exist_ok=True)
|
|
28
|
+
# Limited to this explicit experimental process, not Python installation.
|
|
29
|
+
tempfile.tempdir=str(p)
|
|
30
|
+
return p
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def reference(p):
|
|
34
|
+
p=artifact_path(p)
|
|
35
|
+
return {'path':p.relative_to(WORKDIR.get()).as_posix(),'sha256':sha(p),'bytes':p.stat().st_size}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def publish(path,text,check):
|
|
39
|
+
path=artifact_path(path);pending=artifact_path(str(path)+'.pending')
|
|
40
|
+
if path.exists() or pending.exists():raise FileExistsError('Never replace completed or incomplete evidence')
|
|
41
|
+
with pending.open('x',encoding='utf8') as f:f.write(text);f.flush();os.fsync(f.fileno())
|
|
42
|
+
check()
|
|
43
|
+
if path.exists():raise FileExistsError('Concurrent output')
|
|
44
|
+
os.rename(pending,path)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def require(operation):
|
|
48
|
+
current=CURRENT.get()
|
|
49
|
+
if current is None or current[1]['operation'] not in operation:raise AdmissionError({'status':'BLOCKED','reason':'EXACT_OPERATION_ADMISSION_REQUIRED','operation':list(operation),'permitted_operations':[]})
|
|
50
|
+
engine,r=current
|
|
51
|
+
if engine.stop.exists():raise AdmissionError({'status':'BLOCKED','reason':'STOP_PERMANENT'})
|
|
52
|
+
return engine,r
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def explicit_input(path):
|
|
56
|
+
engine,r=require(('load','replay'))
|
|
57
|
+
p=artifact_path(path)
|
|
58
|
+
matches=[v for v in r['components'] if safe_path(engine.q['input_root'],v['path'])==p]
|
|
59
|
+
if len(matches)!=1:raise ValueError('Exact supplied component reference required')
|
|
60
|
+
b=p.read_bytes();v=matches[0]
|
|
61
|
+
import hashlib
|
|
62
|
+
if len(b)!=v['bytes'] or hashlib.sha256(b).hexdigest()!=v['sha256']:raise ValueError('Changed supplied model bytes')
|
|
63
|
+
# Original bounded native loader sees a private, stable copy of these bytes.
|
|
64
|
+
configure_temp();target=artifact_path(Path(bounded_directory())/'input.json');target.write_bytes(b)
|
|
65
|
+
return target
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
def class_groups(y):
|
|
4
|
+
groups={}
|
|
5
|
+
for i,value in enumerate(y):groups.setdefault(value,[]).append(i)
|
|
6
|
+
return [groups[value] for value in sorted(groups)]
|
|
7
|
+
def bootstrap(y, task, seed):
|
|
8
|
+
rng = random.Random(seed)
|
|
9
|
+
if task == 'regression':
|
|
10
|
+
return [rng.randrange(len(y)) for _ in y]
|
|
11
|
+
groups = class_groups(y)
|
|
12
|
+
ids = [rng.choice(group) for group in groups for _ in group]
|
|
13
|
+
rng.shuffle(ids)
|
|
14
|
+
return ids
|
|
15
|
+
|
|
16
|
+
def oof_folds(y, task, seed):
|
|
17
|
+
groups = [list(range(len(y)))] if task == 'regression' else class_groups(y)
|
|
18
|
+
rng = random.Random(seed)
|
|
19
|
+
folds = [[], []]
|
|
20
|
+
for group in groups:
|
|
21
|
+
if len(group) < 2:
|
|
22
|
+
raise ValueError('OOF requires at least two examples in every TRAIN class')
|
|
23
|
+
rng.shuffle(group)
|
|
24
|
+
for j, i in enumerate(group):
|
|
25
|
+
folds[j % 2].append(i)
|
|
26
|
+
return [sorted(v) for v in folds]
|