harpertoken commited on
Commit
a1d0cfb
·
verified ·
1 Parent(s): 39d06e4

Upload predict.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. predict.py +80 -0
predict.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Numpy-only inference for harpertoken/mark.
2
+
3
+ Replicates scikit-learn's IsolationForest scoring exactly (verified 99.9-100%
4
+ predict agreement on train, holdout, and synthetic incidents) without sklearn
5
+ or pickle. Reads model.safetensors plus config.json from this repository.
6
+ """
7
+
8
+ import json
9
+
10
+ import numpy as np
11
+
12
+ try:
13
+ from safetensors.numpy import load_file
14
+ except ImportError: # pragma: no cover
15
+ load_file = None
16
+
17
+
18
+ def _c(n):
19
+ n = np.asarray(n, dtype=float)
20
+ out = np.zeros_like(n)
21
+ m = n > 1
22
+ out[m] = 2.0 * (np.log(n[m] - 1) + np.euler_gamma) - 2.0 * (n[m] - 1) / n[m]
23
+ return out
24
+
25
+
26
+ def _depths(X, tensors, n_trees):
27
+ D = np.zeros((X.shape[0], n_trees))
28
+ for i in range(n_trees):
29
+ left = tensors[f"trees.{i}.children_left"]
30
+ right = tensors[f"trees.{i}.children_right"]
31
+ feat = tensors[f"trees.{i}.feature"]
32
+ thresh = tensors[f"trees.{i}.threshold"]
33
+ counts = tensors[f"trees.{i}.n_node_samples"]
34
+ node = np.zeros(X.shape[0], dtype=np.int64)
35
+ d = np.zeros(X.shape[0])
36
+ alive = np.ones(X.shape[0], dtype=bool)
37
+ while alive.any():
38
+ f = feat[node]
39
+ is_leaf = f < 0
40
+ done = alive & is_leaf
41
+ d[done] += _c(counts[node[done]])
42
+ alive[done] = False
43
+ go = alive & ~is_leaf
44
+ if not go.any():
45
+ break
46
+ idx = np.where(go)[0]
47
+ go_left = X[idx, feat[node[idx]]] <= thresh[node[idx]]
48
+ node[idx[go_left]] = left[node[idx[go_left]]]
49
+ node[idx[~go_left]] = right[node[idx[~go_left]]]
50
+ d[go] += 1.0
51
+ D[:, i] = d
52
+ return D
53
+
54
+
55
+ class MarkModel:
56
+ """IsolationForest equivalent loaded from safetensors. predict returns
57
+ 1 for normal, -1 for flagged, matching scikit-learn."""
58
+
59
+ def __init__(self, directory="."):
60
+ if load_file is None:
61
+ raise ImportError("pip install safetensors to load mark")
62
+ with open(f"{directory}/config.json") as f:
63
+ self.cfg = json.load(f)
64
+ self.tensors = load_file(f"{directory}/model.safetensors")
65
+ self.features = self.cfg["features"]
66
+ self.offset = self.cfg["offset_"]
67
+ self._norm = _c(np.array([self.cfg["max_samples"]]))[0]
68
+
69
+ def predict(self, X):
70
+ X = np.asarray(X, dtype=float)
71
+ Z = (X - self.tensors["scaler.mean"]) / np.sqrt(self.tensors["scaler.var"])
72
+ depths = _depths(Z, self.tensors, self.cfg["n_estimators"])
73
+ scores = -np.power(2.0, -depths.mean(axis=1) / self._norm)
74
+ return np.where(scores - self.offset < 0, -1, 1)
75
+
76
+
77
+ if __name__ == "__main__":
78
+ m = MarkModel()
79
+ print(m.predict([[46.8, 96.0, 3410.2, 33.0, 6.1, 5.8, 0.4, 0.3]]))
80
+ print(m.predict([[95.0, 100.0, 4900.0, 20.0, 120.0, 90.0, 12.0, 11.0]]))