← Source tree

build_browser_model.py

"""Export this project's synthetic scikit-learn forest for offline JS inference."""
import json
from pathlib import Path

import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.ensemble._iforest import _average_path_length

from demo import fixture
from reviewqueue import features


def build():
    x = np.asarray([features(r) for r in fixture()["reference"]])
    model = IsolationForest(n_estimators=100, max_samples=min(len(x), 256),
                            contamination="auto", random_state=42, n_jobs=1).fit(x)
    trees = []
    for estimator in model.estimators_:
        tree = estimator.tree_
        depths = np.zeros(tree.node_count, dtype=int)
        for i in range(tree.node_count):
            if tree.children_left[i] != -1:
                depths[tree.children_left[i]] = depths[i] + 1
                depths[tree.children_right[i]] = depths[i] + 1
        trees.append(dict(left=tree.children_left.tolist(), right=tree.children_right.tolist(),
                          feature=tree.feature.tolist(), threshold=tree.threshold.tolist(),
                          leaf_depth=(depths + _average_path_length(tree.n_node_samples)).tolist()))
    artifact = dict(version="ReviewQueue 0.6 synthetic USD forest", training_rows=len(x),
                    sklearn_version=__import__('sklearn').__version__,
                    bounds=[[float(min(c)), float(max(c))] for c in x.T],
                    normalizer=float(_average_path_length([len(x)])[0]), trees=trees)
    Path("web_demo/model.js").write_text("globalThis.REVIEWQUEUE_MODEL = " + json.dumps(artifact, separators=(",", ":")) + ";\n")
    # New numerical probes exercise exported traversal independently of UI code.
    rng = np.random.default_rng(319)
    probes = [[float(rng.uniform(-1000, 100000)), float(rng.uniform(.1, 30))] for _ in range(200)]
    probes += [[119., 1.], [99000., 20.], [130., 1.], [-119., 1.]]
    values = [features(dict(amount=str(a), quantity=str(q))) for a, q in probes]
    expected = model.decision_function(values)
    Path("browser_parity.json").write_text(json.dumps([
        dict(amount=str(a), quantity=str(q), expected=float(score))
        for (a, q), score in zip(probes, expected)], indent=2) + "\n")
    print(f"Exported {len(trees)} trees and {len(probes)} independent numerical probes.")


if __name__ == "__main__":
    build()