CharlesCNorton commited on
Commit
f5498f9
·
0 Parent(s):

Image-level person classification on EUPE-ViT-B features with a single free parameter

Browse files
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ *.egg-info/
5
+
6
+ # Synthesis build products; regenerate with `make synth`.
7
+ build/
Makefile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # make test run the backbone-free consistency suite
2
+ # make rtl regenerate rtl/ from per_dim_thresholds.json
3
+ # make synth synthesize every decision variant with nosis
4
+ # make clean
5
+
6
+ PYTHON ?= python
7
+
8
+ test:
9
+ $(PYTHON) -m pytest -q
10
+
11
+ rtl:
12
+ $(PYTHON) rtl_gen.py
13
+
14
+ synth: rtl
15
+ $(PYTHON) synth.py
16
+
17
+ clean:
18
+ rm -rf build
19
+
20
+ .PHONY: test rtl synth clean
README.md ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ license_name: fair-research-license
4
+ license_link: https://huggingface.co/facebook/EUPE-ViT-B/blob/main/LICENSE
5
+ base_model: facebook/EUPE-ViT-B
6
+ tags:
7
+ - image-classification
8
+ - binary-classification
9
+ - minimal-models
10
+ - interpretability
11
+ - vision-transformer
12
+ - circuit-synthesis
13
+ library_name: pytorch
14
+ datasets:
15
+ - detection-datasets/coco
16
+ pipeline_tag: image-classification
17
+ ---
18
+
19
+ # 1-Parameter Classifier
20
+
21
+ Image-level person classification on EUPE-ViT-B features. A 768 pixel image
22
+ gives 2304 patch tokens at the final layer; layernorm across the 768 channels
23
+ and max-pool across patches gives one 768-D vector. The classifier reads 40 of
24
+ its dimensions, 20 person-positive and 20 person-negative, sums the positives,
25
+ subtracts the negatives, and compares the result to one threshold. The dimension
26
+ indices and their signs are fixed structure; the threshold, 25.284, is the only
27
+ value fitted to data.
28
+
29
+ ```python
30
+ patches = backbone(image)["x_norm_patchtokens"] # (2304, 768)
31
+ pooled = layernorm(patches, 768).max(dim=0) # (768,)
32
+ score = pooled[pos_dims].sum() - pooled[neg_dims].sum()
33
+ present = score > threshold # the only free parameter
34
+ ```
35
+
36
+ ```python
37
+ from infer import PersonDetector
38
+ det = PersonDetector.load('baseline')
39
+ score, present = det.predict('image.jpg')
40
+ ```
41
+
42
+ ## Variants
43
+
44
+ | variant | dims | F1 | precision | recall | prop-FPR |
45
+ |---|---:|---:|---:|---:|---:|
46
+ | `baseline` | 40 | 0.8886 | 0.9011 | 0.8763 | 5.93 % |
47
+ | `tight_fpr` | 55 | 0.8527 | 0.8967 | 0.8127 | 2.72 % |
48
+
49
+ `baseline` is measured on all 5000 images of COCO val2017 through a backbone
50
+ forward at 768 px. `tight_fpr` keeps the same 20 positive dimensions and extends
51
+ the negative set with 15 mined from person-associated objects photographed
52
+ without people; it trades 0.036 F1 for a prop false-positive rate of 2.72 %. Its
53
+ row is measured by `discovery/prop_specificity.json` at `extra_neg_k` 15, which
54
+ recorded no pool, and is not comparable with the `VAL5000` figure.
55
+
56
+ ## Dimension selection
57
+
58
+ `discovery/dim_selection.json`: 100,000 random 92-dimension subsets of the 768-D
59
+ space, a ridge classifier per subset, the top 1 % kept, dimension occurrence
60
+ counted across that cohort. Dimension 48 appears in 100 % of the top 1000
61
+ subsets; the next strongest, 525, appears in 31 %.
62
+
63
+ `discovery/dim48_characterization.json`: five analyses on dimension 48. F1
64
+ against K. Activation distributions for person-positive and person-negative
65
+ images, Cohen's d 1.98. Per-class activation delta across all 80 COCO
66
+ categories. Pairwise correlation among the ten most frequent dimensions, maximum
67
+ absolute value 0.57. Spatial IoU of peak activations against ground-truth person
68
+ boxes, mean 0.17.
69
+
70
+ `discovery/variant_leaderboard.json`: 20 classifier forms from 1 to 769
71
+ parameters. Ternary ±1 over 50 positive and 50 negative dimensions leads at F1
72
+ 0.893.
73
+
74
+ Dimension 48 responds to people and to person-associated objects and is
75
+ suppressed on non-human animals and on non-anthropogenic structures. Alone it
76
+ reaches F1 0.83 as a 2-parameter classifier. The other 39 dimensions carry
77
+ largely orthogonal axes and reach 0.89 at one free parameter.
78
+
79
+ ## Prop specificity
80
+
81
+ `discovery/prop_specificity.json` and `prop_image_manifest.json` measure
82
+ separation between "person present" and "person-associated object present, no
83
+ person". 8,479 ImageNet training images across 20 such synsets were filtered
84
+ with YOLO26l at confidence 0.25 to keep only frames with no detected person.
85
+ `baseline` fires on 5.9 % of them. Adding prop-specific negative dimensions
86
+ takes that to 2.7 % at K=15, which is the knee and what
87
+ `classifier_tight_fpr.json` carries.
88
+
89
+ ## Circuit
90
+
91
+ Inputs are the 40 selected channels as signed INT8, post-layernorm,
92
+ post-max-pool and indexed. Output is one bit, combinational, with no multipliers
93
+ and no memory. Two forms, each synthesized with thresholds as runtime inputs and
94
+ with them baked in.
95
+
96
+ | variant | thresholds | slices | LUT4 | CCU2C | bound | ns |
97
+ |---|---|---:|---:|---:|---|---:|
98
+ | `sum` | runtime | 312 | 31 | 312 | carry | 10.40 |
99
+ | `sum_folded` | baked | 312 | 21 | 312 | carry | 10.40 |
100
+ | `popcount` | runtime | 146 | 291 | 118 | lut | 10.80 |
101
+ | `popcount_folded` | baked | 118 | 109 | 118 | carry | 10.80 |
102
+
103
+ ```
104
+ additive score = sum(pos) - sum(neg) 40 x 8-bit adder tree
105
+ out = score > T 16-bit signed comparator
106
+
107
+ popcount b_i = f_i > t_i 40 x 8-bit comparators
108
+ out = popcount(b_0..b_19)
109
+ - popcount(b_20..b_39) > K two 20->5 bit counts, small compare
110
+ ```
111
+
112
+ The popcount form replaces the signed adder tree with 40 independent one-bit
113
+ decisions, taking the carry chain from 312 cells to 118. Each channel retains
114
+ only which side of its threshold it fell on.
115
+
116
+ ```
117
+ form F1
118
+ additive, float 0.884
119
+ popcount, K=13 0.876 -0.008
120
+ ```
121
+
122
+ Measured on `BALANCED_VAL`, where the additive figure is 0.884 against the 0.889
123
+ measured on `VAL5000`. Both come from the same measurement.
124
+
125
+ Synthesis is [nosis](https://github.com/CharlesCNorton/nosis) targeting a
126
+ Lattice ECP5 LFE5U-25F. Counts are LUT4s, carry cells and slices on that device.
127
+ `calibrate.py` selects the 40 per-dimension thresholds and the integer K and
128
+ writes `per_dim_thresholds.json`; `rtl_gen.py` emits all four modules from that
129
+ file. INT8 constants are the calibrated float values scaled by 8.
130
+
131
+ ## Layout
132
+
133
+ ```
134
+ common/ pooled features, ternary scoring, metrics, named pools
135
+ classifier.json, .safetensors dimensions, signs and the threshold
136
+ classifier_tight_fpr.json, .safetensors the low-false-fire variant
137
+ head.py the decision as a fused Linear with ternary weights
138
+ verify.py scores a config over a named pool, writes eval.json
139
+ calibrate.py per-dimension calibration, writes per_dim_thresholds.json
140
+ rtl_gen.py Verilog generation from per_dim_thresholds.json
141
+ synth.py nosis synthesis, writes circuit.json
142
+ infer.py loader for both variants
143
+ discovery/ how the 40 dimensions were chosen
144
+ rtl/ the four decision modules, all generated
145
+ tests/ consistency suite, no backbone or dataset required
146
+ ```
147
+
148
+ Each measured JSON opens with a provenance block naming its generating script,
149
+ the classifier config it read and that config's hash, and the pool.
150
+ `tests/test_artifacts.py` enforces the pairing. The five files under
151
+ `discovery/` record a null generator; their sweeps are not committed.
152
+
153
+ ## Running
154
+
155
+ ```
156
+ pip install -e .
157
+ make test # consistency suite
158
+ python verify.py # baseline on VAL5000
159
+ python calibrate.py # thresholds, then all four modules
160
+ make synth # synthesize with nosis
161
+ ```
162
+
163
+ `COCO_ROOT` is the dataset root. `BACKBONE` is the backbone repo id or a local
164
+ path. `BACKBONE_SRC` supplies `argus.py` from a local directory; otherwise it is
165
+ fetched from the backbone repo.
166
+
167
+ ## Evaluation pools
168
+
169
+ Declared in `common/pools.py` and named in each artifact's provenance block.
170
+ Figures are comparable only within a pool.
171
+
172
+ | pool | images |
173
+ |---|---|
174
+ | `VAL5000` | all 5000 of COCO val2017 |
175
+ | `CALIB1000` | first 1000 val2017 ids |
176
+ | `VAL500` | first 500 val2017 ids |
177
+ | `BALANCED_VAL` | val2017 subsampled to equal classes |
178
+
179
+ ## Source backbone
180
+
181
+ EUPE-ViT-B from Meta FAIR ([arXiv:2603.22387](https://arxiv.org/abs/2603.22387),
182
+ Zhu et al., March 2026), distilled from PEcore-G + PElang-G + DINOv3-H+ via a
183
+ 1.9B proxy teacher. License: FAIR Research License, non-commercial. This
184
+ classifier is an artifact derived from that backbone's feature geometry.
calibrate.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Calibrate the popcount reformulation and regenerate the RTL.
2
+
3
+ Per-dim thresholds are chosen on a balanced COCO val subsample: for a
4
+ person-positive dim the split maximizing F1 under `value > t`, for a
5
+ person-negative dim under `value < t`. Either way the split point is the same
6
+ cut, and at inference every channel uses `>` because the negative count is
7
+ subtracted.
8
+
9
+ Writes per_dim_thresholds.json, then calls rtl_gen so the baked constants cannot
10
+ drift from the calibration that produced them.
11
+ """
12
+ import argparse
13
+ import json
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ import torch
18
+
19
+ sys.path.insert(0, str(Path(__file__).resolve().parent)) # repo root, for `common`
20
+ from common import (COCO_ROOT, D, balanced_indices, coco_split, device, # noqa: E402
21
+ f1_sweep, person_labels, pool, prf1, write_artifact)
22
+ from common.pools import BALANCED_VAL # noqa: E402
23
+
24
+ import rtl_gen # noqa: E402
25
+
26
+ HERE = Path(__file__).resolve().parent
27
+ CLASSIFIER = HERE / 'classifier.json'
28
+ QUANT_SCALE = 8 # INT8 fixed-point scale for the layernormed feature values
29
+
30
+
31
+ def main():
32
+ ap = argparse.ArgumentParser(description=__doc__)
33
+ ap.add_argument('--cache', type=Path,
34
+ default=COCO_ROOT / 'val_feature_cache_768' / 'val.pt')
35
+ ap.add_argument('--seed', type=int, default=0)
36
+ args = ap.parse_args()
37
+
38
+ dev = device()
39
+ c = json.loads(CLASSIFIER.read_text())
40
+ pos_dims, neg_dims = c['pos_dims'], c['neg_dims']
41
+ all_dims = pos_dims + neg_dims
42
+ n_pos = len(pos_dims)
43
+
44
+ print('[load] val features and person labels', flush=True)
45
+ val = torch.load(args.cache, map_location='cpu', weights_only=False)
46
+ coco, _ = coco_split('val2017')
47
+ ids = [int(e['img_id']) for e in val]
48
+ feats = torch.stack([pool(e['spatial'].float().permute(1, 2, 0).reshape(-1, D))
49
+ for e in val]).to(dev)[:, all_dims]
50
+ y = person_labels(coco, ids, dev)
51
+ print(f' N={feats.shape[0]} person_rate={y.float().mean():.3f}', flush=True)
52
+
53
+ sel = balanced_indices(y, args.seed)
54
+ X, yb = feats[sel.to(dev)], y[sel.to(dev)]
55
+ print(f'[balanced] N={len(sel)} person_rate={yb.float().mean():.3f}', flush=True)
56
+
57
+ per_dim = []
58
+ for local, global_dim in enumerate(all_dims):
59
+ vals = X[:, local]
60
+ is_pos = local < n_pos
61
+ candidates = torch.quantile(vals, torch.linspace(0.05, 0.95, 19, device=dev))
62
+ best = (0.0, 0.0)
63
+ for t in candidates.tolist():
64
+ m = prf1(vals > t if is_pos else vals < t, yb)
65
+ if m.f1 > best[0]:
66
+ best = (m.f1, t)
67
+ per_dim.append({'dim_index_in_40': local, 'dim_global': int(global_dim),
68
+ 'is_pos': is_pos, 'threshold': best[1],
69
+ 'threshold_int8': int(round(best[1] * QUANT_SCALE)),
70
+ 'per_dim_F1': best[0]})
71
+ lo = min(p['per_dim_F1'] for p in per_dim)
72
+ hi = max(p['per_dim_F1'] for p in per_dim)
73
+ print(f'[per-dim] calibrated, standalone F1 range {lo:.3f} - {hi:.3f}', flush=True)
74
+
75
+ bits = torch.stack([X[:, p['dim_index_in_40']] > p['threshold'] for p in per_dim], 1)
76
+ diff = (bits[:, :n_pos].sum(1) - bits[:, n_pos:].sum(1)).float()
77
+ best_k, best_m = 0, prf1(diff > 0, yb)
78
+ for t in range(-20, 21):
79
+ m = prf1(diff > t, yb)
80
+ if m.f1 > best_m.f1:
81
+ best_k, best_m = t, m
82
+ print(f'[popcount] F1={best_m.f1:.4f} P={best_m.precision:.4f} '
83
+ f'R={best_m.recall:.4f} K={best_k}', flush=True)
84
+
85
+ sums = X[:, :n_pos].sum(1) - X[:, n_pos:].sum(1)
86
+ add = f1_sweep(sums, yb)
87
+ print(f'[additive] F1={add.f1:.4f} P={add.precision:.4f} R={add.recall:.4f} '
88
+ f't={add.threshold:.3f}', flush=True)
89
+
90
+ write_artifact(HERE / 'per_dim_thresholds.json', {
91
+ 'quant_scale': QUANT_SCALE,
92
+ 'per_dim_thresholds': per_dim,
93
+ 'popcount': {'final_threshold': int(best_k), **best_m.asdict()},
94
+ 'additive': add.asdict(),
95
+ 'F1_delta_popcount_vs_additive': best_m.f1 - add.f1,
96
+ }, generator='calibrate.py', classifier=CLASSIFIER,
97
+ pool=BALANCED_VAL.name, split=BALANCED_VAL.split, n_images=int(len(sel)),
98
+ positive_rate=round(yb.float().mean().item(), 4),
99
+ selection=BALANCED_VAL.selection, seed=args.seed)
100
+
101
+ for path in rtl_gen.generate():
102
+ print(f'[rtl] wrote {path}', flush=True)
103
+ print('[done]', flush=True)
104
+
105
+
106
+ if __name__ == '__main__':
107
+ main()
circuit.json ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "provenance": {
3
+ "generator": "synth.py",
4
+ "tool": "nosis",
5
+ "target": {
6
+ "family": "ecp5",
7
+ "device": "LFE5U-25F"
8
+ },
9
+ "inputs": "40 signed INT8 feature channels at the classifier dims",
10
+ "note": "LUT4, carry and slice counts on an ECP5, not abstract gates"
11
+ },
12
+ "variants": {
13
+ "sum": {
14
+ "slices": 312,
15
+ "lut4": 31,
16
+ "ccu2c": 312,
17
+ "ffs": 0,
18
+ "bound": "carry",
19
+ "critical_path_ns": 10.4,
20
+ "device": "LFE5U-25F",
21
+ "rtl": "rtl/sum.v",
22
+ "thresholds": "runtime input"
23
+ },
24
+ "sum_folded": {
25
+ "slices": 312,
26
+ "lut4": 21,
27
+ "ccu2c": 312,
28
+ "ffs": 0,
29
+ "bound": "carry",
30
+ "critical_path_ns": 10.4,
31
+ "device": "LFE5U-25F",
32
+ "rtl": "rtl/sum_folded.v",
33
+ "thresholds": "baked"
34
+ },
35
+ "popcount": {
36
+ "slices": 146,
37
+ "lut4": 291,
38
+ "ccu2c": 118,
39
+ "ffs": 0,
40
+ "bound": "lut",
41
+ "critical_path_ns": 10.8,
42
+ "device": "LFE5U-25F",
43
+ "rtl": "rtl/popcount.v",
44
+ "thresholds": "runtime inputs"
45
+ },
46
+ "popcount_folded": {
47
+ "slices": 118,
48
+ "lut4": 109,
49
+ "ccu2c": 118,
50
+ "ffs": 0,
51
+ "bound": "carry",
52
+ "critical_path_ns": 10.8,
53
+ "device": "LFE5U-25F",
54
+ "rtl": "rtl/popcount_folded.v",
55
+ "thresholds": "baked"
56
+ }
57
+ },
58
+ "accuracy": {
59
+ "protocol": "balanced COCO val subsample, see calibrate.py",
60
+ "additive_F1": 0.8843,
61
+ "popcount_F1": 0.8764,
62
+ "delta": -0.0079
63
+ }
64
+ }
classifier.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backbone": "facebook/EUPE-ViT-B",
3
+ "feature_dim": 768,
4
+ "input_resolution": 768,
5
+ "patch_size": 16,
6
+ "patch_grid": [48, 48],
7
+ "preprocessing": "layernorm over 768 channels then max-pool over 2304 patches",
8
+ "pos_dims": [48, 525, 475, 645, 273, 292, 158, 510, 506, 337, 8, 309, 267, 217, 79, 13, 657, 207, 722, 311],
9
+ "neg_dims": [642, 224, 113, 565, 49, 637, 45, 520, 219, 290, 529, 617, 269, 745, 576, 701, 105, 694, 82, 283],
10
+ "pos_weight": 1.0,
11
+ "neg_weight": -1.0,
12
+ "threshold": 25.284494400024414,
13
+ "decision": "sum(feat[pos_dims]) - sum(feat[neg_dims]) > threshold",
14
+ "free_parameters": 1,
15
+ "fixed_parameters": {
16
+ "dim_indices": 40,
17
+ "signs": 40
18
+ }
19
+ }
classifier.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8bc146ba921a879f159952375ff485ebe5d6ef97af02b33a7699f629775e0392
3
+ size 676
classifier_tight_fpr.json ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "variant": "stage_0_tight_fpr",
3
+ "parent": "classifier.json",
4
+ "description": "Extended-negative-dims classifier tuned for low prop-FPR. Positive dims unchanged; negative dims extended with 15 prop-correlated dims discovered via YOLO-filtered ImageNet prop-only images. See discovery/prop_specificity.json.",
5
+ "backbone": "facebook/EUPE-ViT-B",
6
+ "feature_dim": 768,
7
+ "input_resolution": 768,
8
+ "patch_size": 16,
9
+ "patch_grid": [
10
+ 48,
11
+ 48
12
+ ],
13
+ "preprocessing": "layernorm over 768 channels then max-pool over 2304 patches",
14
+ "pos_dims": [
15
+ 48,
16
+ 525,
17
+ 475,
18
+ 645,
19
+ 273,
20
+ 292,
21
+ 158,
22
+ 510,
23
+ 506,
24
+ 337,
25
+ 8,
26
+ 309,
27
+ 267,
28
+ 217,
29
+ 79,
30
+ 13,
31
+ 657,
32
+ 207,
33
+ 722,
34
+ 311
35
+ ],
36
+ "neg_dims_original": [
37
+ 642,
38
+ 224,
39
+ 113,
40
+ 565,
41
+ 49,
42
+ 637,
43
+ 45,
44
+ 520,
45
+ 219,
46
+ 290,
47
+ 529,
48
+ 617,
49
+ 269,
50
+ 745,
51
+ 576,
52
+ 701,
53
+ 105,
54
+ 694,
55
+ 82,
56
+ 283
57
+ ],
58
+ "neg_dims_extra": [
59
+ 11,
60
+ 697,
61
+ 572,
62
+ 671,
63
+ 51,
64
+ 186,
65
+ 752,
66
+ 90,
67
+ 161,
68
+ 310,
69
+ 88,
70
+ 189,
71
+ 613,
72
+ 201,
73
+ 360
74
+ ],
75
+ "neg_dims": [
76
+ 642,
77
+ 224,
78
+ 113,
79
+ 565,
80
+ 49,
81
+ 637,
82
+ 45,
83
+ 520,
84
+ 219,
85
+ 290,
86
+ 529,
87
+ 617,
88
+ 269,
89
+ 745,
90
+ 576,
91
+ 701,
92
+ 105,
93
+ 694,
94
+ 82,
95
+ 283,
96
+ 11,
97
+ 697,
98
+ 572,
99
+ 671,
100
+ 51,
101
+ 186,
102
+ 752,
103
+ 90,
104
+ 161,
105
+ 310,
106
+ 88,
107
+ 189,
108
+ 613,
109
+ 201,
110
+ 360
111
+ ],
112
+ "pos_weight": 1.0,
113
+ "neg_weight": -1.0,
114
+ "threshold": 10.094175338745117,
115
+ "decision": "sum(feat[pos_dims]) - sum(feat[neg_dims]) > threshold",
116
+ "free_parameters": 1,
117
+ "fixed_parameters": {
118
+ "dim_indices": 55,
119
+ "signs": 55
120
+ }
121
+ }
classifier_tight_fpr.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:477e75cd66877cb2066c1e7e9d858f6a598f65d75c387a16f9f5fd78feae7d7d
3
+ size 796
common/__init__.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Pooled features, ternary scoring, metrics, named pools, artifact contract."""
2
+ from .paths import BACKBONE, BACKBONE_SRC, COCO_ROOT, REPO, UPSTREAM_BACKBONE, device
3
+ from .data import (MEAN, STD, coco_split, image_paths, load_image, normalize,
4
+ person_labels)
5
+ from .features import D, RES, backbone_pooled, pool, score, score_pool
6
+ from .metrics import Metrics, f1_at, f1_sweep, prf1
7
+ from .pools import BALANCED_VAL, CALIB1000, POOLS, VAL500, VAL5000, Pool, by_name
8
+ from .pool import LoadedPool, balanced_indices, load_pool
9
+ from .artifacts import (REGISTRY, ArtifactSpec, provenance, read_artifact,
10
+ sha256_of, write_artifact)
11
+
12
+ __all__ = [
13
+ 'BACKBONE', 'BACKBONE_SRC', 'COCO_ROOT', 'REPO', 'UPSTREAM_BACKBONE', 'device',
14
+ 'MEAN', 'STD', 'coco_split', 'image_paths', 'load_image', 'normalize',
15
+ 'person_labels',
16
+ 'D', 'RES', 'backbone_pooled', 'pool', 'score', 'score_pool',
17
+ 'Metrics', 'f1_at', 'f1_sweep', 'prf1',
18
+ 'BALANCED_VAL', 'CALIB1000', 'POOLS', 'VAL500', 'VAL5000', 'Pool', 'by_name',
19
+ 'LoadedPool', 'balanced_indices', 'load_pool',
20
+ 'REGISTRY', 'ArtifactSpec', 'provenance', 'read_artifact', 'sha256_of',
21
+ 'write_artifact',
22
+ ]
common/artifacts.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Artifact provenance stamping and the generator/artifact registry."""
2
+ import hashlib
3
+ import json
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Optional, Tuple
7
+
8
+ from .paths import REPO
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class ArtifactSpec:
13
+ """Owning script, source pool, and required top-level payload keys."""
14
+
15
+ generator: Optional[str]
16
+ pool: Optional[str]
17
+ payload_keys: Tuple[str, ...]
18
+ note: str = ''
19
+
20
+
21
+ NO_GENERATOR = 'discovery sweep not committed; no producer for this file in the repo'
22
+
23
+ REGISTRY = {
24
+ 'eval.json': ArtifactSpec('verify.py', 'VAL5000', ('metrics',)),
25
+ 'eval_tight_fpr.json': ArtifactSpec(
26
+ 'verify.py', None,
27
+ ('metrics', 'prop_false_positive_rate', 'baseline')),
28
+ 'discovery/dim_selection.json': ArtifactSpec(None, None, (), NO_GENERATOR),
29
+ 'discovery/dim48_characterization.json': ArtifactSpec(None, None, (), NO_GENERATOR),
30
+ 'discovery/prop_specificity.json': ArtifactSpec(None, None, (), NO_GENERATOR),
31
+ 'discovery/prop_image_manifest.json': ArtifactSpec(None, None, (), NO_GENERATOR),
32
+ 'discovery/variant_leaderboard.json': ArtifactSpec(None, None, (), NO_GENERATOR),
33
+ 'per_dim_thresholds.json': ArtifactSpec(
34
+ 'calibrate.py', 'BALANCED_VAL',
35
+ ('quant_scale', 'per_dim_thresholds', 'popcount', 'additive',
36
+ 'F1_delta_popcount_vs_additive')),
37
+ 'circuit.json': ArtifactSpec('synth.py', None, ('variants', 'accuracy')),
38
+ }
39
+
40
+
41
+ def sha256_of(path) -> str:
42
+ """Content hash of a file."""
43
+ return hashlib.sha256(Path(path).read_bytes()).hexdigest()
44
+
45
+
46
+ def provenance(generator: str, classifier=None, pool_info: Optional[dict] = None,
47
+ **extra) -> dict:
48
+ """Assemble a provenance block from repository-recoverable fields only."""
49
+ block = {'generator': generator}
50
+ if classifier is not None:
51
+ path = Path(classifier)
52
+ block['classifier'] = str(path.resolve().relative_to(REPO)).replace('\\', '/')
53
+ block['classifier_sha256'] = sha256_of(path)
54
+ if pool_info:
55
+ block.update(pool_info)
56
+ block.update(extra)
57
+ return block
58
+
59
+
60
+ def write_artifact(path, payload: dict, *, generator: str, classifier=None,
61
+ pool_info: Optional[dict] = None, compact: bool = False, **extra):
62
+ """Write `payload` beneath a provenance block and return the document."""
63
+ doc = {'provenance': provenance(generator, classifier, pool_info, **extra)}
64
+ doc.update(payload)
65
+ sep = (',', ':') if compact else None
66
+ text = json.dumps(doc, indent=None if compact else 2, separators=sep)
67
+ Path(path).write_text(text + ('' if compact else '\n'), encoding='utf-8')
68
+ return doc
69
+
70
+
71
+ def read_artifact(path) -> dict:
72
+ return json.loads(Path(path).read_text(encoding='utf-8'))
common/data.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """COCO loading and input normalization.
2
+
3
+ Images are resized to a square `resolution` with bilinear interpolation and
4
+ normalized with ImageNet statistics, matching the protocol every stage was
5
+ measured under.
6
+ """
7
+ from pathlib import Path
8
+ from typing import Iterable, List, Sequence, Tuple, Union
9
+
10
+ import numpy as np
11
+ import torch
12
+ from PIL import Image
13
+
14
+ from .paths import COCO_ROOT
15
+
16
+ MEAN = (0.485, 0.456, 0.406)
17
+ STD = (0.229, 0.224, 0.225)
18
+ PERSON_CATEGORY_ID = 1
19
+
20
+
21
+ def _stats(device: str) -> Tuple[torch.Tensor, torch.Tensor]:
22
+ mean = torch.tensor(MEAN).view(1, 3, 1, 1).to(device)
23
+ std = torch.tensor(STD).view(1, 3, 1, 1).to(device)
24
+ return mean, std
25
+
26
+
27
+ def normalize(img: Image.Image, resolution: int, device: str) -> torch.Tensor:
28
+ """PIL image -> (1, 3, R, R) normalized float tensor."""
29
+ img = img.convert('RGB').resize((resolution, resolution), Image.BILINEAR)
30
+ arr = np.asarray(img, dtype=np.uint8).copy()
31
+ x = torch.from_numpy(arr).permute(2, 0, 1).unsqueeze(0).to(device).float() / 255.0
32
+ mean, std = _stats(device)
33
+ return (x - mean) / std
34
+
35
+
36
+ def load_image(image: Union[str, Path, Image.Image, np.ndarray, torch.Tensor],
37
+ resolution: int, device: str) -> torch.Tensor:
38
+ """Accept a path, PIL image, HWC array, or CHW tensor; return a batch of 1."""
39
+ if isinstance(image, (str, Path)):
40
+ img = Image.open(image)
41
+ elif isinstance(image, Image.Image):
42
+ img = image
43
+ elif isinstance(image, np.ndarray):
44
+ img = Image.fromarray(image)
45
+ elif isinstance(image, torch.Tensor):
46
+ arr = image.cpu().numpy() if image.ndim == 3 else image[0].cpu().numpy()
47
+ if arr.shape[0] == 3:
48
+ arr = arr.transpose(1, 2, 0)
49
+ img = Image.fromarray((arr * 255).astype('uint8'))
50
+ else:
51
+ raise TypeError(f'unsupported image type: {type(image)}')
52
+ return normalize(img, resolution, device)
53
+
54
+
55
+ def coco_split(split: str = 'val2017'):
56
+ """Return (COCO handle, image-file lookup) for a COCO split."""
57
+ from pycocotools.coco import COCO
58
+ coco = COCO(str(COCO_ROOT / 'annotations' / f'instances_{split}.json'))
59
+ id_to_file = {i['id']: i['file_name'] for i in coco.loadImgs(coco.getImgIds())}
60
+ return coco, id_to_file
61
+
62
+
63
+ def person_labels(coco, img_ids: Sequence[int], device: str = 'cpu') -> torch.Tensor:
64
+ """Image-level person presence for each id, as a bool tensor."""
65
+ labels = [
66
+ any(a['category_id'] == PERSON_CATEGORY_ID
67
+ for a in coco.loadAnns(coco.getAnnIds(imgIds=i, iscrowd=False)))
68
+ for i in img_ids
69
+ ]
70
+ return torch.tensor(labels, dtype=torch.bool, device=device)
71
+
72
+
73
+ def image_paths(id_to_file: dict, img_ids: Iterable[int],
74
+ split: str = 'val2017') -> List[Path]:
75
+ """Absolute paths for a sequence of image ids within a split."""
76
+ root = COCO_ROOT / split
77
+ return [root / id_to_file[i] for i in img_ids]
common/features.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Pooled feature extraction and the ternary scoring rule.
2
+
3
+ The classifier reads one 768-D vector per image: layernorm across the 768
4
+ channels of every patch token, then max-pool across the 2304 patches. Score is
5
+ the sum of the person-positive dims minus the sum of the person-negative dims.
6
+ """
7
+ from typing import Optional, Sequence
8
+
9
+ import torch
10
+ import torch.nn.functional as F
11
+
12
+ D = 768
13
+ RES = 768
14
+
15
+
16
+ def pool(patch_tokens: torch.Tensor) -> torch.Tensor:
17
+ """(N, D) or (B, N, D) patch tokens -> (D,) or (B, D) pooled vector."""
18
+ ln = F.layer_norm(patch_tokens.float(), [D])
19
+ return ln.max(dim=-2).values
20
+
21
+
22
+ @torch.inference_mode()
23
+ def backbone_pooled(backbone, x: torch.Tensor, autocast: bool = True) -> torch.Tensor:
24
+ """Forward a normalized batch through the backbone and pool it."""
25
+ if autocast:
26
+ dev = 'cuda' if x.is_cuda else 'cpu'
27
+ with torch.autocast(dev, dtype=torch.bfloat16):
28
+ out = backbone.forward_features(x)
29
+ else:
30
+ out = backbone.forward_features(x)
31
+ return pool(out['x_norm_patchtokens'].float())
32
+
33
+
34
+ def score(pooled: torch.Tensor, pos: Sequence[int], neg: Sequence[int]) -> torch.Tensor:
35
+ """sum(pooled[pos]) - sum(pooled[neg]), over the last axis."""
36
+ if not torch.is_tensor(pos):
37
+ pos = torch.tensor(list(pos), dtype=torch.long, device=pooled.device)
38
+ if not torch.is_tensor(neg):
39
+ neg = torch.tensor(list(neg), dtype=torch.long, device=pooled.device)
40
+ return pooled.index_select(-1, pos).sum(-1) - pooled.index_select(-1, neg).sum(-1)
41
+
42
+
43
+ def score_pool(backbone, loaded, pos, neg, target_dims: Optional[torch.Tensor] = None):
44
+ """Score a pool; with `target_dims`, also return the pooled activations there."""
45
+ scores, targets = [], []
46
+ for x in loaded:
47
+ pooled = backbone_pooled(backbone, x)[0]
48
+ scores.append(score(pooled, pos, neg))
49
+ if target_dims is not None:
50
+ targets.append(pooled[target_dims])
51
+ stacked = torch.stack(scores)
52
+ return (stacked, torch.stack(targets)) if target_dims is not None else (stacked, None)
common/metrics.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Binary classification metrics, single-sourced so every stage scores identically."""
2
+ from typing import NamedTuple
3
+
4
+ import torch
5
+
6
+
7
+ class Metrics(NamedTuple):
8
+ """F1, precision, recall, and the threshold they were measured at."""
9
+
10
+ f1: float
11
+ precision: float
12
+ recall: float
13
+ threshold: float = float('nan')
14
+
15
+ def asdict(self) -> dict:
16
+ d = {'F1': self.f1, 'precision': self.precision, 'recall': self.recall}
17
+ if self.threshold == self.threshold: # excludes NaN
18
+ d['threshold'] = self.threshold
19
+ return d
20
+
21
+
22
+ def prf1(pred: torch.Tensor, labels: torch.Tensor) -> Metrics:
23
+ """Metrics for boolean prediction and label tensors."""
24
+ tp = (pred & labels).sum().float()
25
+ fp = (pred & ~labels).sum().float()
26
+ fn = (~pred & labels).sum().float()
27
+ precision = tp / (tp + fp).clamp(min=1)
28
+ recall = tp / (tp + fn).clamp(min=1)
29
+ f1 = 2 * precision * recall / (precision + recall).clamp(min=1e-9)
30
+ return Metrics(float(f1), float(precision), float(recall))
31
+
32
+
33
+ def f1_at(scores: torch.Tensor, labels: torch.Tensor, threshold: float) -> Metrics:
34
+ """Metrics at a fixed threshold."""
35
+ return prf1(scores > threshold, labels)._replace(threshold=float(threshold))
36
+
37
+
38
+ def f1_sweep(scores: torch.Tensor, labels: torch.Tensor, n_candidates: int = 500) -> Metrics:
39
+ """Best metrics over candidate thresholds drawn evenly from the sorted unique scores."""
40
+ uniq = torch.unique(scores).sort().values
41
+ stride = max(1, len(uniq) // n_candidates)
42
+ best = Metrics(0.0, 0.0, 0.0, 0.0)
43
+ for t in uniq.tolist()[::stride]:
44
+ m = prf1(scores > t, labels)
45
+ if m.f1 > best.f1:
46
+ best = m._replace(threshold=float(t))
47
+ return best
common/models.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Backbone loading."""
2
+ import sys
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ from .paths import BACKBONE, BACKBONE_SRC
7
+
8
+ _argus = None
9
+
10
+
11
+ def argus_module():
12
+ """Import argus.py from the environment, BACKBONE_SRC, or the backbone repo."""
13
+ global _argus
14
+ if _argus is not None:
15
+ return _argus
16
+ try:
17
+ import argus
18
+ except ImportError:
19
+ if BACKBONE_SRC:
20
+ sys.path.insert(0, str(BACKBONE_SRC))
21
+ else:
22
+ from huggingface_hub import hf_hub_download
23
+ sys.path.insert(0, str(Path(hf_hub_download(BACKBONE, 'argus.py')).parent))
24
+ import argus
25
+ _argus = argus
26
+ return argus
27
+
28
+
29
+ def load_backbone(repo: Optional[str] = None):
30
+ """Load the stock backbone in eval mode."""
31
+ from transformers import AutoModel
32
+ return AutoModel.from_pretrained(repo or BACKBONE, trust_remote_code=True).eval().backbone
common/paths.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Filesystem and repository locations, overridable by environment.
2
+
3
+ BACKBONE HF repo id or local path for the backbone wrapper (alias: ARGUS_PATH)
4
+ BACKBONE_SRC local directory supplying argus.py (alias: ARGUS_SRC)
5
+ COCO_ROOT dataset root holding annotations/, train2017/, val2017/
6
+ DEVICE torch device string
7
+ """
8
+ import os
9
+ from pathlib import Path
10
+
11
+ REPO = Path(__file__).resolve().parent.parent
12
+
13
+ UPSTREAM_BACKBONE = 'facebook/EUPE-ViT-B'
14
+ BACKBONE = os.environ.get('BACKBONE') or os.environ.get('ARGUS_PATH') or 'phanerozoic/argus'
15
+ BACKBONE_SRC = os.environ.get('BACKBONE_SRC') or os.environ.get('ARGUS_SRC') or None
16
+ COCO_ROOT = Path(os.environ.get('COCO_ROOT', '/home/zootest/datasets/coco'))
17
+
18
+ ARGUS = BACKBONE
19
+
20
+
21
+ def device() -> str:
22
+ if 'DEVICE' in os.environ:
23
+ return os.environ['DEVICE']
24
+ import torch
25
+ return 'cuda' if torch.cuda.is_available() else 'cpu'
common/pool.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Evaluation-pool loading."""
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+ from typing import Iterator, List, Optional
5
+
6
+ import torch
7
+ from PIL import Image
8
+
9
+ from .data import coco_split, image_paths, normalize, person_labels
10
+ from .features import RES
11
+ from .pools import Pool
12
+
13
+
14
+ @dataclass
15
+ class LoadedPool:
16
+ """Image ids, paths and labels for one named pool, images optionally resident."""
17
+
18
+ pool: Pool
19
+ img_ids: List[int]
20
+ paths: List[Path]
21
+ labels: torch.Tensor
22
+ device: str
23
+ images: Optional[List[torch.Tensor]] = None
24
+
25
+ def __len__(self) -> int:
26
+ return len(self.img_ids)
27
+
28
+ def __iter__(self) -> Iterator[torch.Tensor]:
29
+ """Yield each image as a normalized (1, 3, RES, RES) tensor."""
30
+ if self.images is not None:
31
+ yield from self.images
32
+ return
33
+ for path in self.paths:
34
+ yield normalize(Image.open(path), RES, self.device)
35
+
36
+ @property
37
+ def positive_rate(self) -> float:
38
+ return round(self.labels.float().mean().item(), 4)
39
+
40
+ def provenance(self) -> dict:
41
+ """Pool fields recorded in an artifact's provenance block."""
42
+ return {'pool': self.pool.name, 'split': self.pool.split,
43
+ 'n_images': len(self), 'positive_rate': self.positive_rate,
44
+ 'selection': self.pool.selection}
45
+
46
+
47
+ def balanced_indices(labels: torch.Tensor, seed: int = 0) -> torch.Tensor:
48
+ """Indices subsampling `labels` to equal positive and negative counts, seeded."""
49
+ generator = torch.Generator(device='cpu').manual_seed(seed)
50
+ cpu = labels.cpu()
51
+ pos = cpu.nonzero(as_tuple=True)[0]
52
+ neg = (~cpu).nonzero(as_tuple=True)[0]
53
+ n = min(len(pos), len(neg))
54
+ sel = torch.cat([pos[torch.randperm(len(pos), generator=generator)[:n]],
55
+ neg[torch.randperm(len(neg), generator=generator)[:n]]])
56
+ return sel[torch.randperm(len(sel), generator=generator)]
57
+
58
+
59
+ def load_pool(pool: Pool, device: str, preload: bool = False, seed: int = 0) -> LoadedPool:
60
+ """Resolve a named pool to ids, paths and labels; `preload` holds images in memory."""
61
+ coco, id_to_file = coco_split(pool.split)
62
+ img_ids = sorted(coco.getImgIds())
63
+ if pool.n is not None:
64
+ img_ids = img_ids[:pool.n]
65
+ labels = person_labels(coco, img_ids, device)
66
+ if pool.balanced:
67
+ sel = balanced_indices(labels, seed)
68
+ img_ids = [img_ids[i] for i in sel.tolist()]
69
+ labels = labels[sel.to(labels.device)]
70
+ paths = image_paths(id_to_file, img_ids, pool.split)
71
+ images = [normalize(Image.open(p), RES, device) for p in paths] if preload else None
72
+ return LoadedPool(pool, img_ids, paths, labels, device, images)
common/pools.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Named evaluation pools, cited by name in every artifact's provenance block."""
2
+ from typing import NamedTuple, Optional
3
+
4
+
5
+ class Pool(NamedTuple):
6
+ name: str
7
+ split: str
8
+ n: Optional[int]
9
+ balanced: bool
10
+ selection: str
11
+
12
+
13
+ VAL5000 = Pool(
14
+ 'VAL5000', 'val2017', 5000, False,
15
+ 'the first 5000 val2017 image ids in sorted order, which is the whole split')
16
+
17
+ CALIB1000 = Pool(
18
+ 'CALIB1000', 'val2017', 1000, False,
19
+ 'the first 1000 val2017 image ids in sorted order')
20
+
21
+ VAL500 = Pool(
22
+ 'VAL500', 'val2017', 500, False,
23
+ 'the first 500 val2017 image ids in sorted order')
24
+
25
+ BALANCED_VAL = Pool(
26
+ 'BALANCED_VAL', 'val2017', None, True,
27
+ 'val2017 subsampled without replacement to equal person-positive and '
28
+ 'person-negative counts')
29
+
30
+ POOLS = {p.name: p for p in (VAL5000, CALIB1000, VAL500, BALANCED_VAL)}
31
+
32
+
33
+ def by_name(name: str) -> Pool:
34
+ if name not in POOLS:
35
+ raise ValueError(f'unknown pool {name!r}; expected one of {sorted(POOLS)}')
36
+ return POOLS[name]
discovery/dim48_characterization.json ADDED
@@ -0,0 +1,546 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "provenance": {
3
+ "generator": null,
4
+ "note": "discovery sweep not committed; no producer in the repo"
5
+ },
6
+ "target_dim": 48,
7
+ "A_f1_vs_k": [
8
+ {
9
+ "K": 1,
10
+ "F1": 0.8285356163978577,
11
+ "precision": 0.7985524535179138,
12
+ "recall": 0.8608582615852356,
13
+ "dims": [
14
+ 48
15
+ ]
16
+ },
17
+ {
18
+ "K": 2,
19
+ "F1": 0.8562204241752625,
20
+ "precision": 0.8503633737564087,
21
+ "recall": 0.8621586561203003,
22
+ "dims": [
23
+ 48,
24
+ 525
25
+ ]
26
+ },
27
+ {
28
+ "K": 3,
29
+ "F1": 0.8652572631835938,
30
+ "precision": 0.8526936173439026,
31
+ "recall": 0.8781967759132385,
32
+ "dims": [
33
+ 48,
34
+ 525,
35
+ 475
36
+ ]
37
+ },
38
+ {
39
+ "K": 5,
40
+ "F1": 0.8649698495864868,
41
+ "precision": 0.8608844876289368,
42
+ "recall": 0.8690940737724304,
43
+ "dims": [
44
+ 48,
45
+ 525,
46
+ 475,
47
+ 323,
48
+ 240
49
+ ]
50
+ },
51
+ {
52
+ "K": 10,
53
+ "F1": 0.9092138409614563,
54
+ "precision": 0.9465290904045105,
55
+ "recall": 0.8747290968894958,
56
+ "dims": [
57
+ 48,
58
+ 525,
59
+ 475,
60
+ 323,
61
+ 240,
62
+ "..."
63
+ ]
64
+ },
65
+ {
66
+ "K": 20,
67
+ "F1": 0.9288457632064819,
68
+ "precision": 0.9698113203048706,
69
+ "recall": 0.8912007212638855,
70
+ "dims": [
71
+ 48,
72
+ 525,
73
+ 475,
74
+ 323,
75
+ 240,
76
+ "..."
77
+ ]
78
+ },
79
+ {
80
+ "K": 30,
81
+ "F1": 0.9349648356437683,
82
+ "precision": 0.9795821309089661,
83
+ "recall": 0.8942349553108215,
84
+ "dims": [
85
+ 48,
86
+ 525,
87
+ 475,
88
+ 323,
89
+ 240,
90
+ "..."
91
+ ]
92
+ },
93
+ {
94
+ "K": 50,
95
+ "F1": 0.9407240152359009,
96
+ "precision": 0.9839091300964355,
97
+ "recall": 0.9011703729629517,
98
+ "dims": [
99
+ 48,
100
+ 525,
101
+ 475,
102
+ 323,
103
+ 240,
104
+ "..."
105
+ ]
106
+ },
107
+ {
108
+ "K": 92,
109
+ "F1": 0.9444695711135864,
110
+ "precision": 0.98539799451828,
111
+ "recall": 0.9068053960800171,
112
+ "dims": [
113
+ 48,
114
+ 525,
115
+ 475,
116
+ 323,
117
+ 240,
118
+ "..."
119
+ ]
120
+ },
121
+ {
122
+ "K": 184,
123
+ "F1": 0.9485046863555908,
124
+ "precision": 0.9855140447616577,
125
+ "recall": 0.9141742587089539,
126
+ "dims": [
127
+ 48,
128
+ 525,
129
+ 475,
130
+ 323,
131
+ 240,
132
+ "..."
133
+ ]
134
+ }
135
+ ],
136
+ "B_dim48_distribution": {
137
+ "person_pos": {
138
+ "n": 2693,
139
+ "mean": 8.113646507263184,
140
+ "std": 2.3924715518951416,
141
+ "min": -1.2812166213989258,
142
+ "p25": 7.193147659301758,
143
+ "p50": 8.808687210083008,
144
+ "p75": 9.730491638183594,
145
+ "max": 12.061566352844238
146
+ },
147
+ "person_neg": {
148
+ "n": 2307,
149
+ "mean": 2.982386589050293,
150
+ "std": 2.7675836086273193,
151
+ "min": -3.072082042694092,
152
+ "p25": 0.5744761228561401,
153
+ "p50": 3.059739589691162,
154
+ "p75": 5.2233123779296875,
155
+ "max": 11.300838470458984
156
+ },
157
+ "separation_cohen_d": 1.983604907989502
158
+ },
159
+ "C_per_class_top15": [
160
+ {
161
+ "class": "person",
162
+ "cat_id": 1,
163
+ "n_imgs": 2693,
164
+ "dim48_mean_inclass": 8.1136474609375,
165
+ "dim48_mean_outclass": 2.982386350631714,
166
+ "delta": 5.131261110305786
167
+ },
168
+ {
169
+ "class": "tennis racket",
170
+ "cat_id": 43,
171
+ "n_imgs": 167,
172
+ "dim48_mean_inclass": 9.163715362548828,
173
+ "dim48_mean_outclass": 5.627990245819092,
174
+ "delta": 3.5357251167297363
175
+ },
176
+ {
177
+ "class": "tie",
178
+ "cat_id": 32,
179
+ "n_imgs": 145,
180
+ "dim48_mean_inclass": 8.980120658874512,
181
+ "dim48_mean_outclass": 5.6494951248168945,
182
+ "delta": 3.330625534057617
183
+ },
184
+ {
185
+ "class": "baseball glove",
186
+ "cat_id": 40,
187
+ "n_imgs": 100,
188
+ "dim48_mean_inclass": 8.982823371887207,
189
+ "dim48_mean_outclass": 5.680027008056641,
190
+ "delta": 3.3027963638305664
191
+ },
192
+ {
193
+ "class": "skateboard",
194
+ "cat_id": 41,
195
+ "n_imgs": 127,
196
+ "dim48_mean_inclass": 8.923754692077637,
197
+ "dim48_mean_outclass": 5.663267135620117,
198
+ "delta": 3.2604875564575195
199
+ },
200
+ {
201
+ "class": "baseball bat",
202
+ "cat_id": 39,
203
+ "n_imgs": 97,
204
+ "dim48_mean_inclass": 8.886703491210938,
205
+ "dim48_mean_outclass": 5.683949947357178,
206
+ "delta": 3.2027535438537598
207
+ },
208
+ {
209
+ "class": "backpack",
210
+ "cat_id": 27,
211
+ "n_imgs": 228,
212
+ "dim48_mean_inclass": 8.655838012695312,
213
+ "dim48_mean_outclass": 5.607059001922607,
214
+ "delta": 3.048779010772705
215
+ },
216
+ {
217
+ "class": "remote",
218
+ "cat_id": 75,
219
+ "n_imgs": 145,
220
+ "dim48_mean_inclass": 8.703699111938477,
221
+ "dim48_mean_outclass": 5.657751083374023,
222
+ "delta": 3.045948028564453
223
+ },
224
+ {
225
+ "class": "toothbrush",
226
+ "cat_id": 90,
227
+ "n_imgs": 34,
228
+ "dim48_mean_inclass": 8.746152877807617,
229
+ "dim48_mean_outclass": 5.725543022155762,
230
+ "delta": 3.0206098556518555
231
+ },
232
+ {
233
+ "class": "handbag",
234
+ "cat_id": 31,
235
+ "n_imgs": 292,
236
+ "dim48_mean_inclass": 8.576542854309082,
237
+ "dim48_mean_outclass": 5.570532321929932,
238
+ "delta": 3.0060105323791504
239
+ },
240
+ {
241
+ "class": "sports ball",
242
+ "cat_id": 37,
243
+ "n_imgs": 169,
244
+ "dim48_mean_inclass": 8.645074844360352,
245
+ "dim48_mean_outclass": 5.644669532775879,
246
+ "delta": 3.0004053115844727
247
+ },
248
+ {
249
+ "class": "hair drier",
250
+ "cat_id": 89,
251
+ "n_imgs": 9,
252
+ "dim48_mean_inclass": 8.637924194335938,
253
+ "dim48_mean_outclass": 5.74086856842041,
254
+ "delta": 2.8970556259155273
255
+ },
256
+ {
257
+ "class": "skis",
258
+ "cat_id": 35,
259
+ "n_imgs": 120,
260
+ "dim48_mean_inclass": 8.562700271606445,
261
+ "dim48_mean_outclass": 5.676822185516357,
262
+ "delta": 2.885878086090088
263
+ },
264
+ {
265
+ "class": "cell phone",
266
+ "cat_id": 77,
267
+ "n_imgs": 214,
268
+ "dim48_mean_inclass": 8.483532905578613,
269
+ "dim48_mean_outclass": 5.623681545257568,
270
+ "delta": 2.859851360321045
271
+ },
272
+ {
273
+ "class": "snowboard",
274
+ "cat_id": 36,
275
+ "n_imgs": 49,
276
+ "dim48_mean_inclass": 8.451048851013184,
277
+ "dim48_mean_outclass": 5.71931266784668,
278
+ "delta": 2.731736183166504
279
+ }
280
+ ],
281
+ "C_per_class_bottom15": [
282
+ {
283
+ "class": "traffic light",
284
+ "cat_id": 10,
285
+ "n_imgs": 191,
286
+ "dim48_mean_inclass": 4.146858215332031,
287
+ "dim48_mean_outclass": 5.809599876403809,
288
+ "delta": -1.6627416610717773
289
+ },
290
+ {
291
+ "class": "boat",
292
+ "cat_id": 9,
293
+ "n_imgs": 121,
294
+ "dim48_mean_inclass": 4.040976047515869,
295
+ "dim48_mean_outclass": 5.788370132446289,
296
+ "delta": -1.74739408493042
297
+ },
298
+ {
299
+ "class": "fire hydrant",
300
+ "cat_id": 11,
301
+ "n_imgs": 86,
302
+ "dim48_mean_inclass": 3.6824471950531006,
303
+ "dim48_mean_outclass": 5.782199382781982,
304
+ "delta": -2.099752187728882
305
+ },
306
+ {
307
+ "class": "clock",
308
+ "cat_id": 85,
309
+ "n_imgs": 204,
310
+ "dim48_mean_inclass": 3.664764642715454,
311
+ "dim48_mean_outclass": 5.83461332321167,
312
+ "delta": -2.169848680496216
313
+ },
314
+ {
315
+ "class": "train",
316
+ "cat_id": 7,
317
+ "n_imgs": 157,
318
+ "dim48_mean_inclass": 3.4102745056152344,
319
+ "dim48_mean_outclass": 5.821805477142334,
320
+ "delta": -2.4115309715270996
321
+ },
322
+ {
323
+ "class": "sheep",
324
+ "cat_id": 20,
325
+ "n_imgs": 65,
326
+ "dim48_mean_inclass": 3.174736261367798,
327
+ "dim48_mean_outclass": 5.779951095581055,
328
+ "delta": -2.605214834213257
329
+ },
330
+ {
331
+ "class": "elephant",
332
+ "cat_id": 22,
333
+ "n_imgs": 89,
334
+ "dim48_mean_inclass": 3.023428201675415,
335
+ "dim48_mean_outclass": 5.795424938201904,
336
+ "delta": -2.7719967365264893
337
+ },
338
+ {
339
+ "class": "parking meter",
340
+ "cat_id": 14,
341
+ "n_imgs": 37,
342
+ "dim48_mean_inclass": 2.688075065612793,
343
+ "dim48_mean_outclass": 5.768880844116211,
344
+ "delta": -3.080805778503418
345
+ },
346
+ {
347
+ "class": "airplane",
348
+ "cat_id": 5,
349
+ "n_imgs": 97,
350
+ "dim48_mean_inclass": 2.6694958209991455,
351
+ "dim48_mean_outclass": 5.806950092315674,
352
+ "delta": -3.1374542713165283
353
+ },
354
+ {
355
+ "class": "stop sign",
356
+ "cat_id": 13,
357
+ "n_imgs": 69,
358
+ "dim48_mean_inclass": 2.509443998336792,
359
+ "dim48_mean_outclass": 5.7913737297058105,
360
+ "delta": -3.2819297313690186
361
+ },
362
+ {
363
+ "class": "cow",
364
+ "cat_id": 21,
365
+ "n_imgs": 87,
366
+ "dim48_mean_inclass": 2.1573596000671387,
367
+ "dim48_mean_outclass": 5.809632778167725,
368
+ "delta": -3.652273178100586
369
+ },
370
+ {
371
+ "class": "bird",
372
+ "cat_id": 16,
373
+ "n_imgs": 125,
374
+ "dim48_mean_inclass": 1.5200526714324951,
375
+ "dim48_mean_outclass": 5.854443073272705,
376
+ "delta": -4.33439040184021
377
+ },
378
+ {
379
+ "class": "giraffe",
380
+ "cat_id": 25,
381
+ "n_imgs": 101,
382
+ "dim48_mean_inclass": 0.5355944633483887,
383
+ "dim48_mean_outclass": 5.8535051345825195,
384
+ "delta": -5.317910671234131
385
+ },
386
+ {
387
+ "class": "zebra",
388
+ "cat_id": 24,
389
+ "n_imgs": 85,
390
+ "dim48_mean_inclass": 0.19826489686965942,
391
+ "dim48_mean_outclass": 5.842027187347412,
392
+ "delta": -5.643762290477753
393
+ },
394
+ {
395
+ "class": "bear",
396
+ "cat_id": 23,
397
+ "n_imgs": 49,
398
+ "dim48_mean_inclass": -0.3452981412410736,
399
+ "dim48_mean_outclass": 5.806369781494141,
400
+ "delta": -6.151667922735214
401
+ }
402
+ ],
403
+ "D_top10_correlation": {
404
+ "dims": [
405
+ 48,
406
+ 525,
407
+ 475,
408
+ 323,
409
+ 240,
410
+ 637,
411
+ 318,
412
+ 472,
413
+ 719,
414
+ 251
415
+ ],
416
+ "corr_matrix": [
417
+ [
418
+ 1.000000238418579,
419
+ 0.28202927112579346,
420
+ -0.03376583009958267,
421
+ 0.24143743515014648,
422
+ -0.013180982321500778,
423
+ 0.1519388109445572,
424
+ 0.21512284874916077,
425
+ 0.12231583148241043,
426
+ 0.15586860477924347,
427
+ 0.14881354570388794
428
+ ],
429
+ [
430
+ 0.28202927112579346,
431
+ 0.9999998211860657,
432
+ 0.2280241698026657,
433
+ 0.08577030897140503,
434
+ 0.20908223092556,
435
+ -0.09965874999761581,
436
+ 0.2280401587486267,
437
+ 0.08895031362771988,
438
+ 0.2658396065235138,
439
+ -0.15059183537960052
440
+ ],
441
+ [
442
+ -0.03376583009958267,
443
+ 0.2280241698026657,
444
+ 1.0000001192092896,
445
+ 0.061812229454517365,
446
+ 0.570030927658081,
447
+ -0.011771553196012974,
448
+ 0.07696891576051712,
449
+ -0.10601639002561569,
450
+ 0.06801445037126541,
451
+ -0.23264755308628082
452
+ ],
453
+ [
454
+ 0.24143743515014648,
455
+ 0.08577030897140503,
456
+ 0.061812229454517365,
457
+ 0.9999997615814209,
458
+ 0.0030930074863135815,
459
+ 0.18994615972042084,
460
+ 0.15969695150852203,
461
+ 0.21833136677742004,
462
+ 0.26542553305625916,
463
+ -0.039861708879470825
464
+ ],
465
+ [
466
+ -0.013180982321500778,
467
+ 0.20908223092556,
468
+ 0.570030927658081,
469
+ 0.0030930074863135815,
470
+ 1.0000003576278687,
471
+ -0.09009360522031784,
472
+ 0.003698738757520914,
473
+ -0.08297917991876602,
474
+ 0.01635875552892685,
475
+ -0.21284666657447815
476
+ ],
477
+ [
478
+ 0.1519388109445572,
479
+ -0.09965874999761581,
480
+ -0.011771553196012974,
481
+ 0.18994615972042084,
482
+ -0.09009360522031784,
483
+ 0.9999999403953552,
484
+ 0.13532496988773346,
485
+ 0.04327913001179695,
486
+ -0.03058011643588543,
487
+ 0.22757190465927124
488
+ ],
489
+ [
490
+ 0.21512284874916077,
491
+ 0.2280401587486267,
492
+ 0.07696891576051712,
493
+ 0.15969695150852203,
494
+ 0.003698738757520914,
495
+ 0.13532496988773346,
496
+ 0.9999998807907104,
497
+ 0.030232839286327362,
498
+ 0.23005540668964386,
499
+ 0.025300158187747
500
+ ],
501
+ [
502
+ 0.12231583148241043,
503
+ 0.08895031362771988,
504
+ -0.10601639002561569,
505
+ 0.21833136677742004,
506
+ -0.08297917991876602,
507
+ 0.04327913001179695,
508
+ 0.030232839286327362,
509
+ 1.000000238418579,
510
+ 0.21879234910011292,
511
+ 0.020776957273483276
512
+ ],
513
+ [
514
+ 0.15586860477924347,
515
+ 0.2658396065235138,
516
+ 0.06801445037126541,
517
+ 0.26542553305625916,
518
+ 0.01635875552892685,
519
+ -0.03058011643588543,
520
+ 0.23005540668964386,
521
+ 0.21879234910011292,
522
+ 0.9999998807907104,
523
+ -0.015971055254340172
524
+ ],
525
+ [
526
+ 0.14881354570388794,
527
+ -0.15059183537960052,
528
+ -0.23264755308628082,
529
+ -0.039861708879470825,
530
+ -0.21284666657447815,
531
+ 0.22757190465927124,
532
+ 0.025300158187747,
533
+ 0.020776957273483276,
534
+ -0.015971055254340172,
535
+ 1.0000001192092896
536
+ ]
537
+ ],
538
+ "max_abs_offdiag": 0.570030927658081
539
+ },
540
+ "E_spatial_localization": {
541
+ "n_sampled_images": 500,
542
+ "mean_iou": 0.16534487868892028,
543
+ "median_iou": 0.15606729686260223,
544
+ "p95_iou": 0.35281437188386916
545
+ }
546
+ }
discovery/dim_selection.json ADDED
@@ -0,0 +1,504 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "provenance": {
3
+ "generator": null,
4
+ "note": "discovery sweep not committed; no producer in the repo"
5
+ },
6
+ "n_sampled": 100000,
7
+ "K": 92,
8
+ "n_kept": 1000,
9
+ "f1_distribution": {
10
+ "min": 0.7833036780357361,
11
+ "p50": 0.8676828145980835,
12
+ "p95": 0.9258173704147339,
13
+ "p99": 0.9300636053085327,
14
+ "max": 0.9373591542243958
15
+ },
16
+ "top_kept_f1_range": [
17
+ 0.9300652146339417,
18
+ 0.9373591542243958
19
+ ],
20
+ "best_single_genome_f1": 0.9373591542243958,
21
+ "best_single_genome_sorted": [
22
+ 2,
23
+ 4,
24
+ 8,
25
+ 21,
26
+ 29,
27
+ 39,
28
+ 48,
29
+ 67,
30
+ 69,
31
+ 75,
32
+ 91,
33
+ 119,
34
+ 124,
35
+ 141,
36
+ 149,
37
+ 157,
38
+ 158,
39
+ 168,
40
+ 177,
41
+ 181,
42
+ 192,
43
+ 198,
44
+ 200,
45
+ 218,
46
+ 220,
47
+ 240,
48
+ 249,
49
+ 250,
50
+ 259,
51
+ 267,
52
+ 269,
53
+ 272,
54
+ 273,
55
+ 275,
56
+ 285,
57
+ 286,
58
+ 297,
59
+ 300,
60
+ 304,
61
+ 318,
62
+ 319,
63
+ 323,
64
+ 328,
65
+ 330,
66
+ 340,
67
+ 373,
68
+ 374,
69
+ 377,
70
+ 379,
71
+ 391,
72
+ 393,
73
+ 403,
74
+ 419,
75
+ 429,
76
+ 433,
77
+ 436,
78
+ 454,
79
+ 463,
80
+ 476,
81
+ 520,
82
+ 524,
83
+ 530,
84
+ 537,
85
+ 548,
86
+ 550,
87
+ 558,
88
+ 560,
89
+ 570,
90
+ 574,
91
+ 582,
92
+ 590,
93
+ 594,
94
+ 605,
95
+ 606,
96
+ 609,
97
+ 612,
98
+ 617,
99
+ 624,
100
+ 630,
101
+ 646,
102
+ 649,
103
+ 657,
104
+ 660,
105
+ 673,
106
+ 680,
107
+ 700,
108
+ 715,
109
+ 716,
110
+ 737,
111
+ 740,
112
+ 754,
113
+ 755
114
+ ],
115
+ "dim_frequency_top_25": {
116
+ "48": 1.0,
117
+ "525": 0.3140000104904175,
118
+ "475": 0.3110000193119049,
119
+ "323": 0.23400001227855682,
120
+ "240": 0.21700000762939453,
121
+ "637": 0.2120000123977661,
122
+ "318": 0.20600001513957977,
123
+ "472": 0.20200000703334808,
124
+ "719": 0.20100000500679016,
125
+ "251": 0.19700001180171967,
126
+ "506": 0.19500000774860382,
127
+ "13": 0.1940000057220459,
128
+ "281": 0.1900000125169754,
129
+ "113": 0.1860000044107437,
130
+ "331": 0.1860000044107437,
131
+ "224": 0.18200001120567322,
132
+ "382": 0.1810000091791153,
133
+ "767": 0.18000000715255737,
134
+ "605": 0.1770000010728836,
135
+ "642": 0.17500001192092896,
136
+ "157": 0.17400000989437103,
137
+ "155": 0.1680000126361847,
138
+ "273": 0.16700001060962677,
139
+ "207": 0.16500000655651093,
140
+ "510": 0.16500000655651093
141
+ },
142
+ "union_classifier": {
143
+ "n_dims": 768,
144
+ "F1": 0.9616926908493042,
145
+ "precision": 0.989005982875824,
146
+ "recall": 0.9358474016189575
147
+ },
148
+ "cores": {
149
+ "23": [
150
+ 48,
151
+ 525,
152
+ 475,
153
+ 323,
154
+ 240,
155
+ 637,
156
+ 318,
157
+ 472,
158
+ 719,
159
+ 251,
160
+ 506,
161
+ 13,
162
+ 281,
163
+ 113,
164
+ 331,
165
+ 224,
166
+ 382,
167
+ 767,
168
+ 605,
169
+ 642,
170
+ 157,
171
+ 155,
172
+ 273
173
+ ],
174
+ "46": [
175
+ 48,
176
+ 525,
177
+ 475,
178
+ 323,
179
+ 240,
180
+ 637,
181
+ 318,
182
+ 472,
183
+ 719,
184
+ 251,
185
+ 506,
186
+ 13,
187
+ 281,
188
+ 113,
189
+ 331,
190
+ 224,
191
+ 382,
192
+ 767,
193
+ 605,
194
+ 642,
195
+ 157,
196
+ 155,
197
+ 273,
198
+ 207,
199
+ 510,
200
+ 127,
201
+ 245,
202
+ 759,
203
+ 292,
204
+ 531,
205
+ 617,
206
+ 333,
207
+ 527,
208
+ 45,
209
+ 332,
210
+ 574,
211
+ 595,
212
+ 269,
213
+ 158,
214
+ 432,
215
+ 0,
216
+ 119,
217
+ 310,
218
+ 490,
219
+ 557,
220
+ 528
221
+ ],
222
+ "92": [
223
+ 48,
224
+ 525,
225
+ 475,
226
+ 323,
227
+ 240,
228
+ 637,
229
+ 318,
230
+ 472,
231
+ 719,
232
+ 251,
233
+ 506,
234
+ 13,
235
+ 281,
236
+ 113,
237
+ 331,
238
+ 224,
239
+ 382,
240
+ 767,
241
+ 605,
242
+ 642,
243
+ 157,
244
+ 155,
245
+ 273,
246
+ 207,
247
+ 510,
248
+ 127,
249
+ 245,
250
+ 759,
251
+ 292,
252
+ 531,
253
+ 617,
254
+ 333,
255
+ 527,
256
+ 45,
257
+ 332,
258
+ 574,
259
+ 595,
260
+ 269,
261
+ 158,
262
+ 432,
263
+ 0,
264
+ 119,
265
+ 310,
266
+ 490,
267
+ 557,
268
+ 528,
269
+ 545,
270
+ 558,
271
+ 597,
272
+ 645,
273
+ 28,
274
+ 51,
275
+ 79,
276
+ 82,
277
+ 301,
278
+ 126,
279
+ 542,
280
+ 173,
281
+ 90,
282
+ 380,
283
+ 71,
284
+ 384,
285
+ 514,
286
+ 620,
287
+ 630,
288
+ 713,
289
+ 223,
290
+ 516,
291
+ 520,
292
+ 606,
293
+ 17,
294
+ 172,
295
+ 355,
296
+ 393,
297
+ 421,
298
+ 745,
299
+ 68,
300
+ 252,
301
+ 280,
302
+ 334,
303
+ 657,
304
+ 95,
305
+ 111,
306
+ 151,
307
+ 244,
308
+ 543,
309
+ 748,
310
+ 290,
311
+ 363,
312
+ 469,
313
+ 561,
314
+ 591
315
+ ],
316
+ "184": [
317
+ 48,
318
+ 525,
319
+ 475,
320
+ 323,
321
+ 240,
322
+ 637,
323
+ 318,
324
+ 472,
325
+ 719,
326
+ 251,
327
+ 506,
328
+ 13,
329
+ 281,
330
+ 113,
331
+ 331,
332
+ 224,
333
+ 382,
334
+ 767,
335
+ 605,
336
+ 642,
337
+ 157,
338
+ 155,
339
+ 273,
340
+ 207,
341
+ 510,
342
+ 127,
343
+ 245,
344
+ 759,
345
+ 292,
346
+ 531,
347
+ 617,
348
+ 333,
349
+ 527,
350
+ 45,
351
+ 332,
352
+ 574,
353
+ 595,
354
+ 269,
355
+ 158,
356
+ 432,
357
+ 0,
358
+ 119,
359
+ 310,
360
+ 490,
361
+ 557,
362
+ 528,
363
+ 545,
364
+ 558,
365
+ 597,
366
+ 645,
367
+ 28,
368
+ 51,
369
+ 79,
370
+ 82,
371
+ 301,
372
+ 126,
373
+ 542,
374
+ 173,
375
+ 90,
376
+ 380,
377
+ 71,
378
+ 384,
379
+ 514,
380
+ 620,
381
+ 630,
382
+ 713,
383
+ 223,
384
+ 516,
385
+ 520,
386
+ 606,
387
+ 17,
388
+ 172,
389
+ 355,
390
+ 393,
391
+ 421,
392
+ 745,
393
+ 68,
394
+ 252,
395
+ 280,
396
+ 334,
397
+ 657,
398
+ 95,
399
+ 111,
400
+ 151,
401
+ 244,
402
+ 543,
403
+ 748,
404
+ 290,
405
+ 363,
406
+ 469,
407
+ 561,
408
+ 591,
409
+ 96,
410
+ 397,
411
+ 419,
412
+ 425,
413
+ 523,
414
+ 627,
415
+ 734,
416
+ 124,
417
+ 236,
418
+ 454,
419
+ 9,
420
+ 345,
421
+ 358,
422
+ 480,
423
+ 575,
424
+ 736,
425
+ 26,
426
+ 34,
427
+ 65,
428
+ 141,
429
+ 152,
430
+ 176,
431
+ 204,
432
+ 293,
433
+ 305,
434
+ 445,
435
+ 282,
436
+ 389,
437
+ 437,
438
+ 619,
439
+ 162,
440
+ 178,
441
+ 311,
442
+ 394,
443
+ 409,
444
+ 483,
445
+ 496,
446
+ 613,
447
+ 717,
448
+ 50,
449
+ 188,
450
+ 216,
451
+ 325,
452
+ 602,
453
+ 699,
454
+ 740,
455
+ 254,
456
+ 258,
457
+ 299,
458
+ 403,
459
+ 495,
460
+ 603,
461
+ 622,
462
+ 40,
463
+ 83,
464
+ 146,
465
+ 150,
466
+ 232,
467
+ 259,
468
+ 341,
469
+ 392,
470
+ 444,
471
+ 522,
472
+ 567,
473
+ 629,
474
+ 647,
475
+ 25,
476
+ 170,
477
+ 205,
478
+ 267,
479
+ 274,
480
+ 307,
481
+ 411,
482
+ 549,
483
+ 576,
484
+ 618,
485
+ 686,
486
+ 760,
487
+ 8,
488
+ 200,
489
+ 255,
490
+ 353,
491
+ 354,
492
+ 413,
493
+ 455,
494
+ 463,
495
+ 544,
496
+ 721,
497
+ 93,
498
+ 171,
499
+ 225,
500
+ 234
501
+ ]
502
+ },
503
+ "runtime_s": 4.635331869125366
504
+ }
discovery/prop_image_manifest.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"provenance":{"generator":null,"note":"discovery sweep not committed; no producer in the repo"},"source":"imagenet-1k train","filter":"YOLO26l, no person detected","person_conf_threshold":0.25,"total_kept":8479,"synsets":{"n04039381":"racket","n04591713":"Windsor_tie","n02883205":"bow_tie","n04254680":"soccer_ball","n04118538":"rugby_ball","n04228054":"ski","n02769748":"backpack","n03127747":"crash_helmet","n03124170":"cowboy_hat","n04259630":"sombrero","n03680355":"loafer","n03124043":"cowboy_boot","n03838899":"oboe","n03249569":"drum","n04074963":"remote_control","n02676566":"acoustic_guitar","n03272010":"electric_guitar","n04152593":"screen","n04590129":"window_shade","n04418357":"theater_curtain"},"stats":{"racket":{"inspected":1300,"kept":357},"Windsor_tie":{"inspected":551,"kept":500},"bow_tie":{"inspected":1300,"kept":242},"soccer_ball":{"inspected":1134,"kept":500},"rugby_ball":{"inspected":1300,"kept":149},"ski":{"inspected":1300,"kept":225},"backpack":{"inspected":587,"kept":500},"crash_helmet":{"inspected":837,"kept":500},"cowboy_hat":{"inspected":1300,"kept":230},"sombrero":{"inspected":1300,"kept":298},"loafer":{"inspected":999,"kept":500},"cowboy_boot":{"inspected":960,"kept":500},"oboe":{"inspected":1300,"kept":478},"drum":{"inspected":918,"kept":500},"remote_control":{"inspected":646,"kept":500},"acoustic_guitar":{"inspected":690,"kept":500},"electric_guitar":{"inspected":791,"kept":500},"screen":{"inspected":579,"kept":500},"window_shade":{"inspected":566,"kept":500},"theater_curtain":{"inspected":797,"kept":500}},"note":"image_ids[synset][k] identifies <synset>/<synset>_<id>.JPEG","image_ids":{"n04039381":[10013,10090,10184,10347,10349,104,10470,10472,10498,10682,10705,10856,10924,10972,11033,11074,1123,11236,11364,11421,11426,11519,11553,11566,11643,1167,11709,11880,11969,12094,12117,12159,1222,12224,1223,12356,12411,12449,12538,12546,12576,12590,12632,12660,12663,12664,12679,12719,1283,12832,12925,13048,13068,13248,13369,13371,13404,13480,13489,13566,13677,13712,13714,13964,14134,14139,14144,14243,14362,14366,14468,14589,14918,14943,14993,15115,15133,15222,15254,15301,15338,15388,15489,15692,1575,15809,15941,15995,16015,16114,16119,16197,16257,16277,16279,16362,16364,16435,16507,16574,16580,16616,16646,1676,1683,16849,16860,16940,17014,17050,17104,17252,1737,17387,17418,17515,17536,17595,17717,17766,17854,18108,18125,18152,1817,18173,18227,18265,18282,18352,1843,18432,18514,18642,1874,18759,18773,18842,19076,19145,19172,19246,19248,19262,19270,19330,19361,19399,1948,19671,19782,19787,19853,19928,19936,19940,19943,19954,19956,19974,20065,20256,20287,20318,20355,20371,20608,20639,20689,20702,20840,20851,20917,20955,21241,21566,21573,21574,21608,21657,21988,22008,22143,22278,22459,22861,22957,23045,23055,23107,23170,23244,23255,23360,2421,2503,2591,2626,2741,2756,2765,2784,2901,2952,2965,30093,3015,30350,3236,32782,3297,3313,3322,3373,3410,35176,3530,3554,3635,37712,3778,3815,3854,38660,3888,3892,39462,3978,3992,4019,4064,4067,4068,4096,4108,4142,4160,41634,41798,4191,4248,4269,4343,4365,4367,4401,4442,44525,4477,4567,4579,4681,471,4716,4732,4771,481,48255,4863,4864,4881,4920,4937,5008,50596,5178,5189,5262,5274,5331,53338,5474,5587,571,5717,5767,5807,5967,597,5988,6044,6166,6205,6301,6317,6406,6441,6452,6473,6476,6525,6769,6788,6989,7077,71,7106,7161,7164,7166,7215,7252,7267,7292,7299,7388,7478,7505,7516,7525,7590,7619,7665,7708,7710,7770,7786,784,7884,7961,8011,8015,8023,8027,8072,8125,8129,813,8222,8310,8327,8393,8395,8409,8412,8423,8669,8772,8803,8813,8840,8969,8985,9058,9250,9282,9285,9306,9328,9338,9361,9377,9447,9593,963,9637,9905],"n04591713":[100,10035,10065,101,10109,1015,1035,10383,10440,1046,10469,10484,105,1051,10510,10525,10550,10569,10614,10629,10686,107,1070,10760,10795,108,1084,11,110,11182,1121,1126,11324,1138,11499,11504,11520,1155,11558,1160,11606,11621,1164,1172,11759,1191,121,1225,1237,124,1244,1251,12567,12569,1257,1260,127,12767,1277,128,1280,12876,1302,1310,1316,132,13234,1326,13283,13349,134,1344,1352,1353,13550,13589,13590,1361,1367,13696,1370,1374,1375,1390,1398,14,1401,1403,14045,1407,1411,14142,14192,14205,1428,1437,1440,14419,145,14540,14563,14579,14626,14630,14641,1468,1472,1473,14741,1476,14895,1496,1503,15073,15127,15172,15205,1524,15278,1531,15321,1548,1554,1566,1573,1587,1594,1601,1604,1606,16097,1610,1611,1615,1616,16175,1627,1630,1634,16352,16364,16396,1640,1643,16442,1649,1652,1658,1659,1660,16610,16654,1671,16710,16751,1679,16793,16805,1691,1692,1702,17048,17055,1713,1717,17170,17197,172,1728,1742,1744,17471,1752,17535,1755,1761,17618,1762,1764,1767,17676,1768,1773,1777,17776,1779,17826,1784,1786,179,1808,18116,1813,1814,18197,1822,1824,18281,183,1834,1836,1837,1848,18520,1857,1860,1876,1879,18806,1885,1893,18962,1900,1905,1908,19122,19169,19185,19198,1927,1930,1932,1935,1938,19409,19432,1952,1962,1969,197,1976,1978,1988,1989,1992,1993,2003,2010,2022,2024,2025,2026,2037,2038,2041,2042,2043,2047,2049,2052,2053,2058,2059,2063,2084,2088,2095,2116,2122,2123,2129,2131,2140,2144,215,2150,2154,2167,217,2170,2176,2178,2180,2194,2197,2199,2200,2212,2222,223,2233,2246,2252,2256,2259,2263,2271,2272,2288,2305,2310,2322,2327,2344,2345,2359,2362,2366,2369,2373,2419,2427,2432,2440,2448,2451,2456,2473,2479,2480,2485,25,2501,2506,2515,2525,2534,2537,2538,2549,2555,2557,2561,2566,2567,2569,2576,2589,2594,2605,261,2648,2666,2668,2676,2679,2680,2691,2694,2701,2713,2714,2718,2732,2740,2747,2753,2757,2760,2764,277,2775,2777,278,2787,2790,2793,2800,2802,2805,2808,2813,2817,2818,2826,2841,2842,2845,2848,2850,2852,2857,2879,2883,2899,2903,2918,2925,2938,2940,2952,2956,2957,2971,2972,2977,2978,2999,3002,3004,3010,3013,3016,3020,3022,3030,304,3052,3054,3069,3083,3085,3090,3093,3094,31,3104,3107,3112,3135,3139,3153,3158,3161,3174,3182,319,3191,3195,321,3214,3218,3242,3252,3255,3258,3260,3281,3284,3289,3291,3295,3299,3300,3309,3312,3315,3316,3327,3328,3332,3333,334,3343,3348,335,3358,3360,3370,3371,3376,3379,3393,3396,3399,3402,3404,3409,3410,3412,342,3420,3423,3429,3430,3438,3441,3442,3444,3448,3452,3454,3460,347,3470,3477,3488,3489,3491,3495,3496,3501,3502,3510,3513,3517,3522,353,3533,3534,3535,3544,3550,3554,356,3561,3562,3565,3568,3570],"n02883205":[102441,103964,10414,10423,10499,10716,10836,10868,10933,11249,11266,11268,11275,12209,1225,12313,12635,12683,12985,13214,13573,13679,13839,13978,14069,14134,14158,14395,14486,14489,14567,14710,14718,14865,15254,15261,15317,15665,1571,1594,15973,16052,16238,16384,169,17340,1737,17377,1747,17480,17589,17676,17712,1789,1795,1806,1815,18189,18414,18432,1877,18832,1949,20083,20091,202,20336,20342,20559,20564,20708,2076,2093,21117,2124,21243,21264,21385,21470,21733,2217,22630,22799,22977,23017,23066,2308,2316,23175,23241,23349,23381,23412,23469,23529,23570,23708,23785,23959,24058,24086,24104,24233,24303,24414,2452,25935,25983,26085,26218,26827,26901,27000,27266,27475,2762,2768,28828,29483,29518,29800,3013,30477,3075,30971,3112,31675,31689,3199,3203,3218,32945,33,33463,33605,3394,3440,34416,34466,3452,34807,34949,35920,36058,36300,36629,37159,3726,3773,38455,38463,38529,38590,39363,40402,40522,40637,40649,4089,41075,4111,41385,4174,41829,4247,429,431,43306,4407,44395,4445,44945,4495,4533,45414,45452,4560,46609,4704,47094,47843,48616,48715,4892,49319,4963,49642,50144,5015,5048,5155,51623,5585,5731,5739,5794,5802,5825,5841,5969,59905,6,6034,6053,6074,6098,6130,62028,6231,6252,63109,6343,6376,6458,6784,6860,6909,6912,6965,7047,7129,716,72227,7346,7497,7543,7721,7826,7877,7937,8052,8105,8229,8268,8368,8379,84107,8652,9003,9160,9291,9909],"n04254680":[10019,10043,10045,10049,10076,10112,10147,1015,10154,1016,10164,10170,10173,10190,10208,10241,1046,10460,10585,1060,1070,10747,10755,10766,10824,1090,10944,10992,1101,11026,11040,11069,1110,1113,11130,11201,11208,1131,11316,1137,11372,11404,11422,11443,1147,1150,11535,1160,11641,11751,11775,1184,1186,119,1190,1202,12086,12098,1220,1222,12287,1237,1240,1244,12455,1247,12484,1252,1253,1257,12573,12580,1264,1265,1266,1268,12724,1277,1279,1286,1295,1300,1305,1323,13233,13250,13251,13280,13307,13323,13379,13427,13436,1344,13449,13477,1352,1357,13623,1366,1376,13780,13803,13821,13898,1395,13987,1399,1401,14028,14038,1406,1411,14136,1424,14252,1430,1432,1434,14361,14369,1439,14417,14444,14687,14723,1473,1476,14812,14971,14994,1501,15039,1511,15224,15226,15240,15342,15422,1546,15590,157,1580,1584,1585,1591,15923,15943,1597,16005,16057,1607,16113,1613,1622,1627,1628,16425,1643,16523,1657,16593,16669,16734,1688,16966,16990,17123,17248,1740,1757,1759,18,1809,1824,1836,1848,1864,1892,190,1903,1904,1914,1926,1950,2005,2090,210,2107,2111,2116,2124,2155,2159,2202,2238,2270,2279,2282,2300,2302,2410,2413,2417,2422,2490,2551,2565,2585,2618,2637,2647,2667,2680,2772,2793,28,2812,2815,2868,294,2960,2968,2976,298,2984,3026,3061,3068,308,316,3164,32,324,3257,33,330,3300,3334,34,3413,3463,3495,3527,3541,3564,3617,3622,3715,376,3905,4023,404,4054,4065,4122,415,4164,4168,4181,422,4261,4279,4337,4344,437,4379,4422,4424,4452,4487,4510,4533,4586,4590,462,4627,4630,466,4667,467,4670,4697,4798,483,4877,4926,493,4941,4951,4956,4974,500,5002,5033,5079,5090,51,5100,5104,511,5120,5204,5260,5267,5276,5284,5301,5328,5334,5346,5349,5358,5368,5384,5387,539,5408,5415,5452,5458,5482,5517,5526,5530,5536,5553,5564,558,5584,559,5597,5598,5611,5613,5617,562,5621,5685,5719,5734,5744,577,579,581,5828,5860,5865,5901,5919,5934,5935,595,5985,5991,6019,6034,6039,6056,6061,6063,607,6086,6089,6096,6105,6152,6156,6161,6171,6182,6190,6209,6238,6252,6351,6368,6438,6470,6541,6544,656,6645,6711,6727,677,6813,6826,6915,6940,698,7078,7109,7146,7150,7184,7193,7226,7256,7260,7267,7280,73,7301,7311,7332,7338,7351,7355,7374,7410,7414,7437,7458,746,7474,7501,7514,7530,7560,7591,760,7607,763,7631,7668,7688,7701,7720,7722,7724,7726,7732,7733,7734,7755,7763,7779,7794,7810,7819,7822,7824,7830,7831,7844,7845,7846,7848,7851,7854,7892,7907,7908,7938,7939,794,7967,7986,7994,8013,8023,8027,8061,8063,8080,8095,8100,8107,8114,8126,8174,818,8197,8210,8235,824,8249,8257,8264,8266,8269,8274,8284,8293,8295,8319,8337,8338,8385,8411,8415,8419,8467,8475,8478,848,8506,854,857],"n04118538":[10198,10210,10219,10271,10466,1065,10755,10765,10921,11054,11096,11164,1119,11212,11270,1130,114,1146,11606,118,11850,11861,119,12229,12317,12341,12368,12599,12697,1288,1298,1346,13574,1369,13779,13853,13866,14011,14465,14536,147,15005,1510,1535,15451,15670,1571,15867,15991,16191,16198,1698,1736,17553,18495,1869,1935,19631,2022,20362,2107,21796,2276,2296,2302,234,24621,24875,2497,250,2755,2782,2858,2871,2926,3063,3066,3159,3272,3380,3482,358,3586,3623,3631,3637,3878,3943,3959,4054,4104,4121,4141,4150,4171,4184,4243,4284,4292,4361,4419,4525,4581,4628,4857,4881,4927,5200,5257,5291,5389,5419,5421,6114,6249,6328,6415,6467,6541,6566,6614,6648,6676,6803,6913,697,6978,7,7035,705,7198,7298,7375,7436,7507,756,7615,8031,809,85,8519,8592,8818,8937,9005,9049,9160,933,9823],"n04228054":[10244,10261,10306,10502,1057,10603,10755,10839,1084,10910,1096,10971,10973,10990,11041,1105,1127,11352,11353,11367,1144,11471,11535,11549,11638,11703,11767,11988,11992,120,12024,12050,12061,1225,1232,1303,13266,1328,13330,1345,1383,1395,13993,1434,145,1461,1479,15002,15024,1551,15676,15917,1596,16120,16211,16552,16629,16630,16933,16944,16946,1719,17548,17555,17607,1766,1813,18286,18398,19653,19675,20283,20378,2056,20634,20893,20970,21128,21413,21614,2185,2213,2259,2308,2324,2377,2395,2415,25016,2504,2521,2650,26843,2687,2693,2783,2860,2911,2994,30397,3058,30631,3080,3171,3172,3214,3217,3253,3319,3327,33610,34211,3454,3489,3519,3533,3538,3616,3718,3729,3817,3829,4142,4190,424,4417,4496,4503,4572,4578,462,4623,4744,4754,4794,48,4824,4833,4837,4838,4880,4900,4985,5022,5043,5073,5295,53,5396,5462,5501,556,5632,5686,5702,5877,5943,5977,6032,6033,613,6168,6206,6271,632,6332,6375,6414,66,6689,6695,6717,6782,693,701,7022,7116,7134,7157,7174,7209,7212,7302,7434,7455,7480,7526,7536,757,7626,7636,7667,7785,7886,7950,7989,8087,8116,8118,8177,8178,820,8218,8222,8325,84,8409,8413,8529,8873,904,929,931,9434,9509,9519,952,9590,9592,9611,9758,983,9835,9869,9998],"n02769748":[10023,10044,10045,100708,10089,101083,101091,101134,10135,10198,10204,10245,102866,103175,103198,1032,103229,10331,103341,103635,103643,103690,103716,103979,104665,10714,10791,10817,10922,10946,10986,11091,111,11144,11168,11236,11247,11248,11249,11277,11288,11293,11352,11373,1144,11443,11479,11744,1188,11946,12087,12149,1218,1220,12248,12336,12588,12727,12836,12871,12913,1295,1298,13046,13080,13105,1313,1319,13234,13273,13493,13551,1357,14060,14145,14180,14268,14315,14409,14510,14527,14540,1499,15053,15063,15153,15175,15269,15279,1528,15281,15310,15334,15342,15449,15456,15511,15571,15626,15635,15658,15669,15707,15813,15824,15844,15880,15914,15925,15964,16033,16048,16086,16206,16231,16326,16331,16366,1637,16376,1639,1641,16420,16431,1645,1651,16520,16613,16636,16701,16712,16802,16856,16875,1688,16882,16890,17001,17014,17033,17061,17099,17118,17135,1718,17192,17297,17338,17339,17433,17495,17548,17607,17655,17737,17748,17763,17779,178,17853,17858,17865,17927,17960,17977,17991,18011,18126,18129,18222,18245,1827,18275,18313,18321,18428,18446,18520,18543,18562,18564,18582,18656,18690,18841,18844,18852,18871,18874,18894,18956,18963,18987,19018,19085,19102,19188,19247,19268,19293,19300,19421,19502,19509,19510,19550,19586,19641,19662,19669,19678,19806,19938,1995,19957,2000,2001,20015,2016,20164,2053,20582,20635,20641,2065,20688,2072,20780,2082,2087,20903,2110,21174,21256,21293,21333,21335,2134,21392,21615,21685,21725,21834,2190,21984,2206,22069,22123,22130,2218,22227,22228,22260,22304,22388,2240,22445,2247,22486,22542,22689,22690,22705,22723,22726,2281,22813,22820,22921,22985,2300,23075,23085,23146,23197,23265,23270,23336,23377,23380,23468,23541,23542,23585,23754,2379,23828,23854,23878,2389,23898,24000,24012,24034,24048,24074,24142,242,24252,24293,24321,24449,24509,24514,2463,24665,24668,2472,24741,24743,2478,24785,2485,24873,250,25029,25169,25173,25223,25231,25236,25298,25344,25355,25467,25468,25561,25590,25606,25620,25684,2571,25747,25757,25841,2587,25894,25971,26073,26099,26109,26117,26152,26162,26173,26211,26302,26317,26346,26472,26542,26592,26626,26634,26644,26735,26776,26853,2688,26895,26941,26962,270,27044,27327,27402,27478,27516,27519,27616,2762,27717,27730,2774,27783,27842,27881,27919,28025,28038,28074,28315,28325,28427,28457,28485,28611,28612,28795,28841,28870,28877,28908,2897,29117,29160,29311,29488,29528,29547,29648,2966,29733,2979,29813,29822,29967,30069,30102,30125,30159,30166,30218,30491,30616,30688,30689,30699,30711,30807,30824,3084,3086,30867,30870,30910,30939,30978,31038,3105,31054,31103,31119,3114,3122,31225,31242,31288,31314,31317,31354,31390,31443,3146,31468,31473,31474,3149,31590,31595,31598,31614,31646,31664,31699,317,31730,31745,31770,31818,31886,31922,31934,31944,31960,31981,31997,3205,32058,32117,32142,32152,32234,32277,32329,3233,32399,32419,3242,32427,32469,32509,32546,32555,32584,32831,32839,32863,32889,32916,33015,33057,33148,33158,33187,33392,33397,33462,33485,33487],"n03127747":[1002,10078,10084,1019,1027,10284,10304,1031,10315,1036,1040,10451,10501,10510,10543,10567,1057,1060,10609,1061,10639,10648,1073,10757,10791,10864,10909,10926,10927,10938,10977,10981,10988,11072,11149,11151,11253,11270,1128,11362,11437,11497,11524,11554,11619,11646,11688,11752,11753,11771,1180,11800,11801,1187,11964,11990,12001,12105,12155,12167,12168,1221,1225,123,1233,12490,12576,12626,12628,12640,1269,1270,12705,12817,1284,12846,12904,12913,13004,13141,13191,13225,1327,1329,13292,13330,13343,13351,13370,13378,1339,13396,1340,13413,13436,13462,1349,13503,13511,13526,13639,1366,137,13702,13704,13767,13813,13871,13900,13915,13917,14040,14052,14078,1416,14164,14183,14197,1422,14331,14340,14342,14359,14404,14418,14427,1445,14456,14470,14472,14539,14611,14622,14634,14636,1467,147,14708,1479,14799,14818,14888,14957,1502,1507,15075,15197,15279,15332,15407,15413,1546,15483,15540,15543,1555,156,15630,15656,1577,15837,15883,15889,15894,15921,1604,16069,1612,16162,16195,1620,16223,16244,16257,16304,16376,16401,1641,16445,16454,1647,16506,16545,16570,16590,16629,16635,16657,16691,16701,16704,16772,16780,16787,16795,1681,1689,16995,17070,17130,17208,1726,17275,17314,17365,17520,1757,17665,17702,17788,17804,17869,1790,1800,18229,18281,18342,1836,1841,18414,1842,18441,18448,18456,18483,1849,1852,18614,18633,18661,18707,18717,18796,18933,18965,18984,19018,19022,19049,1906,1910,19109,19142,192,19230,1931,19320,19359,1936,19413,19530,1956,1963,19645,19684,1976,19877,19969,20039,2018,20254,20275,2033,2038,20449,20489,20496,2055,2056,20719,2104,2121,2122,2126,213,2140,21535,21646,22012,22124,22141,2265,2281,2327,2344,2351,2375,239,241,2414,2417,2436,2443,2450,2458,2479,2482,2533,2535,2537,2539,2547,2553,2564,2568,2576,2586,2593,2596,2631,2640,2650,2700,2768,277,2795,2824,2840,2855,2873,2888,2893,2916,2917,2921,2925,2950,2951,2955,2967,2986,2989,2994,3024,3033,3040,3053,3083,3110,3115,3131,3149,3157,3160,3161,3192,3193,3210,3250,3273,3279,3283,3301,3314,333,3336,3338,3348,3376,3381,3408,3414,3415,342,3421,3434,345,3453,3463,3479,3490,3495,3504,3518,3530,3545,3604,3659,3681,37,3704,3751,3770,3782,3797,3798,380,3801,3814,3826,3869,3883,3901,3912,3917,3927,3957,3968,3976,406,4062,410,4104,4134,4211,4229,4230,4243,4300,4324,433,4358,4361,4365,4382,443,4490,4501,4512,4527,4552,4554,4564,4575,4584,4591,4602,4604,4605,461,4624,4642,4646,4649,4654,4685,4694,4697,4702,4721,4739,4743,4751,4756,4764,4788,480,4834,4837,4856,4857,4859,4894,4899,4900,4912,4931,4936,4940,4951,4953,4955,5003,5005,5020,5022,5032,5057,5080,5091,5098,5139,5143,5177,5178,5196,5205,5227,5242,5257,5258,5266,5292,5299,5303,5305,5323,533,5344,5347,5352,5360,5365,5378,5398,5432,5500,5507,5527,5532],"n03124170":[10150,1025,10303,10401,10512,10593,10673,10734,10755,1079,1082,10828,10901,10917,1096,10982,1106,11136,11177,11182,114,11468,11557,11741,11811,11966,11993,12003,12087,12105,12181,12231,1225,12282,12301,12311,12324,12373,12423,12556,12557,1258,12627,12952,1317,13213,13237,1331,13332,13400,1341,13564,13616,13618,13623,13636,13734,13744,13746,13751,13755,13854,13870,13903,13910,13922,13927,13998,14045,14209,14254,14263,14337,14396,1453,14693,14995,150,15251,1552,1578,1598,1608,16155,16290,1687,1701,1702,1707,17362,18766,1883,1940,1942,1952,1990,19921,19952,2036,2123,2146,2175,2223,2268,2273,2325,2390,2410,2500,2504,2512,2566,2632,271,2812,2972,2982,2987,3017,3058,3093,3164,3165,3269,327,3332,3490,3495,3671,3673,3678,3738,3834,39,3902,3924,3948,4101,4223,4352,4416,4483,4517,4519,4597,4645,4994,5068,5090,512,5202,5219,5245,5262,5295,5340,5373,5383,5400,5421,5427,5452,5487,5494,5521,5576,5630,5654,5675,5701,5895,60,6047,6606,6694,6837,7040,7239,7488,7524,7620,7673,7702,7728,7730,7762,7811,7813,7816,7819,7822,7836,7881,7882,7885,7957,8051,8054,8077,8096,8165,8233,8346,8350,8375,8381,8440,8623,8834,892,8995,9053,9147,9176,927,9337,9388,9394,9429,9430,9524,9652,9680,9689,9703,973,9769,9817,9856,991],"n04259630":[10025,10032,10094,10161,10163,10177,10245,10247,10289,10362,1040,10450,10454,10533,10667,10744,10853,11001,1110,11158,11425,1170,1171,11860,11873,12039,12179,12246,12444,12536,12538,12584,12622,1271,12796,12909,13123,13242,13773,14079,1416,14340,14390,14394,144,14547,14617,14697,14912,1496,15009,15138,15236,15247,15438,15562,157,15750,15794,15938,1604,16045,16100,16123,16135,1615,16168,16194,16264,16341,16361,16446,16469,16626,16667,16795,16877,16917,16954,16972,17015,17146,17205,17295,17401,17441,17446,17456,17555,17608,17645,17685,17714,17793,178,17803,17854,18011,18058,18062,18199,18255,18283,18424,18469,18585,18602,19119,19218,19256,19717,19768,19804,1994,20113,2021,20278,2073,20995,2100,21095,21144,21163,2132,21421,21641,21717,2174,21750,21760,21807,21814,21834,21843,21874,21924,22005,22015,2204,22067,22075,22168,22190,22267,2227,22281,22303,22375,22593,22597,22712,22742,22918,22955,23047,23058,23588,23627,23768,23993,2459,24598,25038,25330,25390,25550,25574,256,25833,2585,2586,26142,26160,2622,26257,2629,26291,26311,26368,26588,26637,26735,27067,27098,2718,27200,27243,27246,27358,27398,27579,27636,2789,28203,2894,29525,3031,3070,31227,32351,338,3544,3553,3588,36590,3665,3698,3713,37320,3814,38448,38691,3953,3956,3985,4030,4100,4275,4364,4369,44,441,4499,4559,4664,4723,4785,4830,4870,4948,515,5167,5323,5617,579,585,5886,5904,6149,6320,6447,6474,6521,6572,6590,6673,6821,6840,6859,6945,7083,7238,7274,7309,7373,7475,7485,7533,7564,7652,7696,7708,7734,782,8037,8149,8241,8329,8526,869,8706,874,8798,883,8869,8964,8989,9001,906,916,9165,9316,9320,9325,9393,9403,9440,9447,9471,9556,9576,9622,9625,9669,9694,9721,9798,9906],"n03680355":[10143,10162,1017,10221,1030,10315,1036,10405,10462,10502,10525,10543,10556,10651,1067,10688,1071,10739,10759,10807,10823,10845,10860,10882,10923,110,1100,11006,11026,11038,1104,1111,1115,1124,11304,1131,11321,11330,11380,11397,11413,11421,11464,11466,11515,11526,11560,1157,1159,11602,1162,1170,1173,11770,11774,11777,11783,11814,1183,11884,11924,1201,12067,12099,12118,1212,1215,12150,12159,1220,1222,12316,12356,12358,12382,1241,1245,12504,1252,1253,12586,12600,12648,12717,1276,12796,12848,12943,12949,1295,130,1302,1308,13108,13162,1322,13252,13289,1335,13388,1341,13414,13487,1349,135,1357,13581,1362,13722,13745,13849,13858,1391,13927,13982,1402,1417,14235,14239,14289,1430,14349,14377,14478,1454,14686,14708,14776,14780,1479,14799,1484,14868,1504,15047,15054,1506,15070,15086,15210,1523,15257,15281,15298,15312,15326,15349,1543,1546,1569,1576,15817,15882,15927,1598,1621,1624,1635,16366,1642,1661,16842,16951,1696,17,17056,17172,17201,17203,17310,17318,1739,17457,17464,17484,17672,17781,1784,17897,18040,18169,1821,18234,18298,18356,18376,18410,1844,18505,1851,18891,18977,1901,19190,19218,19239,19478,1953,19863,19924,20094,20099,20181,2023,2025,20285,20481,20743,2085,2086,20888,20921,20969,21060,21190,2123,2126,21306,21312,2151,21682,21763,21988,2205,22110,22137,22148,22159,22282,2230,22334,2250,22644,2279,22836,2291,23054,23087,23111,2312,2318,23281,23495,23497,23533,23696,23910,23911,2393,2397,24007,2413,24135,24137,24159,2445,2453,24569,24613,2478,24787,2483,24831,25034,2506,25161,25242,25321,2548,2551,25548,2558,25601,25653,2567,2577,25780,25836,25863,25889,2590,2595,26077,2614,26175,26212,2622,2625,2640,2641,2657,2662,2669,2678,2689,2693,2702,2720,2737,2762,2778,2784,2812,2822,2826,284,2845,2846,2849,2868,2878,2910,2921,2927,2939,2963,3018,3020,3028,3046,3049,3103,3118,3133,3158,3191,3193,322,3287,3304,3313,3369,3386,34,3406,3458,3460,3462,3465,3498,3525,3595,3606,3611,3618,3641,3646,365,3669,3698,3744,377,3788,3809,3812,382,3861,3869,3870,3875,3879,3880,3915,3935,3940,3951,3961,3968,3974,3981,3991,4027,4034,4042,4058,4061,4088,4093,4111,412,4122,4126,4127,4137,4141,417,4170,4211,4215,4220,425,427,4280,4290,4319,4328,4375,4384,440,4421,4475,45,4513,452,4526,4556,4585,4593,4612,4622,4637,4649,4667,469,4699,4714,4727,473,4734,4747,4767,4773,4775,4776,4778,4823,4830,4832,484,4840,4843,4849,4851,4859,4871,4882,4906,4932,4964,4992,5003,5006,5017,5026,5050,5069,5084,5086,5114,514,5169,5182,5243,5247,53,531,5321,5322,5351,5361,5363,5369,5374,5382,5406,5407,5440,5467,5471,5472,548,5503,5506,5507,5509,5518,5579,5593,5637,5719,5734,586,5861,5897,601,6050,6064,610,6124,6135,6157,6159,620,639,6395,6399,6432,6479,654,670],"n03124043":[10079,1013,103,1035,10553,1058,1066,107,1075,1078,10967,111,1117,1127,11353,11440,1149,1159,11626,1168,117,1177,1188,1196,1200,1203,1207,121,1215,1229,12292,1232,1247,1248,1252,1259,1260,1299,13,130,1306,131,1311,1315,1319,13194,1320,1328,13494,1366,1377,1379,138,13832,13889,1395,1398,14,140,14046,1408,141,1417,1428,1432,1452,14565,1470,1476,1485,150,1532,1559,1570,1592,1597,1616,1626,1627,166,1690,1691,1693,1695,1703,1709,1713,1722,1727,1754,1770,1773,1799,180,1807,1818,1831,1834,1838,1845,1853,1865,188,18871,1922,1928,193,19300,1966,20311,20320,2043,2048,206,20684,2094,2105,2114,2116,21356,2152,2166,21739,21844,21860,2191,2207,221,2212,2233,225,2254,22856,2286,2291,22998,23079,2315,2332,23349,2340,23466,2352,23629,23752,2377,23936,23944,2400,24044,2407,24071,2438,24427,2447,245,246,24604,2470,2472,2475,2496,25277,25298,255,2550,256,25774,2589,2597,2598,2601,2624,26306,26376,2647,26493,2654,2664,2666,2667,2670,26730,2677,2698,2707,2709,2726,2729,2730,27427,2747,2753,2776,27849,2794,28130,2820,2833,28348,28402,2845,28480,2857,2861,2871,2886,2894,2898,29,2939,2941,2946,2968,297,299,300,30044,3020,3027,30292,3035,304,3041,3042,3045,305,3062,3064,30783,3081,3098,31170,315,3164,31687,317,318,3217,3251,3263,3264,3321,334,3344,3348,3362,3390,3409,3412,3414,342,3444,3447,3459,3464,3479,352,3542,3584,3593,3595,3608,3681,3703,371,3713,3726,3759,3805,3818,3841,3859,3940,3955,3993,4001,4011,4014,4021,4035,4040,4053,4060,4066,4069,4078,4084,4110,4131,4134,4149,4158,416,4163,4168,417,4178,4188,4191,4193,4194,42,4211,4219,4235,4242,4248,4254,4257,4259,4293,431,4353,4385,4388,440,4415,4432,4434,4441,4446,4448,4452,4458,4468,4477,4502,4505,4579,4673,4682,4686,4692,4702,4708,4759,4761,4767,477,4783,4793,4816,4851,4859,4864,4895,4903,4911,4923,4925,493,4934,4936,4940,4946,4948,4958,496,4967,4984,4991,5015,5032,5058,509,5090,5104,5112,5122,5136,5145,515,5153,5166,5178,5198,5201,5224,5229,5234,5235,5267,5314,5318,5329,5336,5339,5343,5371,5375,5377,538,5402,5409,5413,5424,5435,5438,544,5455,5477,5479,549,5498,5499,5501,5502,5503,5518,5569,5585,5589,5626,5637,564,5641,5643,569,5696,5711,5744,5753,5762,5806,5808,5814,5818,5846,5856,5865,5882,5883,589,5891,5901,5913,5934,5942,5945,5962,5963,5969,597,5996,6004,6017,602,6031,6042,6051,6063,6066,6082,6124,6131,6148,6188,6190,6194,6205,6207,6210,6214,6226,6227,6231,6236,625,6255,6259,6264,6269,6296,6301,6303,631,6311,6318,633,6338,6343,6349,6356,6358,6373,6375,6379,6382,6392,6399,6400,641,643,6430,644,6445,6446,645,6453],"n03838899":[10,1003,10111,10112,10144,10182,10223,10281,10328,10427,1059,10621,10668,10913,10914,10930,1117,11188,11431,11442,11485,11795,11829,1186,11878,12022,1216,1220,1227,12292,12312,12513,126,12822,1284,12926,13279,13399,1351,13656,13685,1383,13890,14099,14256,14373,14392,14424,1459,1500,15046,15091,1511,15337,15363,15371,15476,15511,15661,15690,15711,15764,15848,15855,1594,16089,16276,16314,16399,1642,16477,16540,16608,16858,16903,1694,16955,17015,17020,17078,1712,17202,17442,1745,17493,17537,17583,17983,18538,18870,19090,19426,19522,1955,19768,1978,19799,19897,19918,19949,20063,20146,2020,20254,20383,20609,20635,20658,21139,21289,21553,21683,217,2176,21823,21913,2204,22366,22539,2257,22632,2282,22942,22954,23021,23149,23213,23241,23434,23475,23627,2364,23823,23878,23890,23908,24223,24245,24366,24865,251,25220,25379,25677,26087,26096,26203,26502,26549,26589,26838,26866,26887,26927,26964,27021,27044,27095,27181,27234,27241,27256,27271,2742,27447,27503,27517,27522,27532,27643,27678,27747,27815,2783,27832,27889,27902,27947,27955,28028,28030,28033,28061,28136,28206,28244,28258,28485,28539,28563,28636,28655,28767,28777,28925,29020,29086,29213,29233,29329,2970,29719,29749,29885,29988,3,30058,30150,30196,30198,30212,30249,30251,30253,30288,3032,30370,30376,30394,30490,30550,30654,30678,30724,30739,30986,31206,31235,31236,31242,31329,3134,31372,31376,314,31473,31515,31520,31544,31579,31643,31659,31674,31698,31715,31829,31842,31875,31940,31941,31948,31983,31987,32004,32012,32031,32067,32105,32169,32177,32232,32237,32243,32255,32575,32582,32647,32648,32651,32800,32841,32864,32876,32965,32972,32976,32981,32991,33004,33035,33048,33102,33133,33213,33218,33243,33289,33290,33299,33315,33321,33330,33399,33408,33430,33457,33470,33562,33590,33603,33639,33651,33728,33745,33802,33817,3384,33848,33886,33914,340,34000,34024,34026,34425,34426,34432,34598,3469,34791,35032,35217,3527,35292,35513,35537,3565,35860,35867,35875,35902,36032,36271,36500,3656,366,37194,37569,37664,37804,37863,37941,38234,3840,39385,39667,39715,40007,40154,40221,40515,40885,40934,40980,41117,41410,41668,41699,41718,41744,42148,42170,42476,42566,43099,43272,43305,4350,43560,43672,43689,43833,43904,43907,44052,44159,4460,44701,4475,44912,44964,45030,45265,4527,45498,4556,45888,45935,45937,45972,46477,46501,46717,46843,47111,47175,48269,48543,48689,48734,48826,49082,49100,4913,49344,49709,50042,5006,50332,50390,51216,51309,5139,51492,51737,51739,51748,5205,52321,52381,52741,52763,52939,53316,53833,54303,54446,54531,54682,54815,54894,549,55037,55041,55583,55608,56017,56138,5630,5693,5735,57693,58099,5819,6218,6240,6263,6311,6407,6501,6669,6918,6927,694,7243,7251,7316,7438,7452,7505,7683,7723,7877,8045,805,8120,8550,8589,8598,8797,8823,8895,891,8982,9054,9128,9270,9300],"n03249569":[10072,10145,10176,10224,1030,10319,10711,10881,11019,11024,11068,11069,11071,11149,11255,1130,11493,11583,11727,11745,11776,1190,11932,12008,12101,12129,12241,12252,12262,12274,12284,12417,12449,12465,12665,12788,12811,12878,12907,12914,12976,13027,13220,13294,13393,13446,13454,13487,13584,1361,13664,13708,13799,1384,1404,14054,14115,14143,14167,1445,14461,14525,14534,14565,14569,14604,14753,1514,15270,15473,15593,15758,15813,15845,15994,160,16023,16093,16136,16170,16203,16214,16260,16304,16387,16460,16483,16521,16550,16712,16753,16760,1677,16807,16846,16942,16973,17054,17095,17174,17216,17313,17341,1735,17352,17392,17395,17404,17437,17439,17447,17452,17470,17506,17598,17606,17639,17679,17758,17835,17863,17932,17973,17994,18013,18156,18297,18457,18546,18656,18658,18669,18677,18796,18884,19098,19113,19118,19154,19187,19266,19303,19455,19465,19482,19490,19518,19602,19800,1992,19987,20057,20079,20123,20180,20181,20182,20304,20314,20374,20436,20509,20559,20591,20820,20875,20897,20973,20982,20995,2101,21060,21066,21215,21225,21320,21349,21368,21411,21517,21594,2172,21803,21946,21961,22044,22082,22135,22163,22274,22311,22327,22477,22485,22501,22669,22749,22840,22848,22861,22953,22956,23024,23059,23060,23070,23110,23220,2330,23367,23863,24093,2412,24134,24170,24268,24324,24613,24935,25014,25089,25280,25330,25479,25482,25517,2552,25541,25575,25626,25967,25976,26155,26328,26366,26373,26504,26513,26539,26609,26695,26818,26958,26990,27088,27139,27390,27608,27645,27746,27763,27772,27782,27793,27906,27971,2800,28078,2809,28097,28194,2821,28215,28245,28248,2832,28482,28525,28585,28593,2877,28797,2882,29201,29524,29605,2971,29775,29863,30018,30053,30120,30121,30233,30244,30280,30293,30316,30451,30494,30618,3066,30684,30739,3081,30914,31014,31033,31106,31109,31137,31172,31185,312,31271,31272,31275,31311,3132,31348,31375,31507,31603,31625,31643,31745,31763,31794,31822,31836,3190,3194,3221,32276,32454,33103,33200,33436,33617,33814,33823,33885,33999,3400,34017,34162,3448,34607,34752,34830,34833,34868,34879,35025,35030,35043,35050,35052,35162,3519,35228,35249,35310,35333,35362,35415,35430,35567,35589,35612,35637,35657,35714,35774,35812,35820,35840,35888,35932,35935,3596,35961,35971,36030,36059,36133,36162,36200,36234,36361,36434,36445,36477,36519,36603,36622,36668,36771,36807,36819,36853,36854,37051,37056,37062,37099,37143,37201,37374,37383,37773,3783,37850,37870,37899,3790,37903,3791,37946,37965,38281,38451,38641,38688,38744,38789,38838,38922,390,39002,39070,39072,3910,39187,39217,39264,39369,39451,39681,39763,3996,40000,40138,40273,4037,4045,4049,40579,40580,40732,40812,40955,41969,42071,42109,42120,42134,4218,42221,42276,42285,42312,42456,42488,42499,42507,42571,42631,42732,42765,42889,42913,42920,4294,42959,43080,43201,43251,43279,43350,43504,43515,43573,43583,43987,4430,44331,44585,44586,44675,44800,45216,45310,45408,45434,45468,45516,45554,45682,45696,45731,45743,45806,45811,45846,45860,45944,45986,46000,46081],"n04074963":[10005,10029,10107,10117,10129,10135,1015,10191,10247,10273,10282,10321,10395,10406,10424,1046,10468,10474,10509,1052,10557,10653,10655,10675,10704,10742,1075,10841,10947,10951,10968,10995,11012,11040,11081,11119,11143,11160,11163,11213,11226,11244,11289,11290,11339,11358,11419,11424,11434,11442,11445,11454,11500,11506,11519,11522,11535,11554,11559,1156,11575,11582,11590,1166,11668,11676,11704,11709,1177,1179,11798,11802,11807,11834,11845,11850,11862,11864,11875,11883,11895,11910,11947,11975,11976,1200,12015,12041,12050,12059,12073,12077,12090,12101,12130,12166,1225,12291,12303,12362,12451,12458,1247,1252,12534,12537,12568,12612,12643,12698,12720,12752,12763,1277,12787,12818,12819,1282,12849,12871,12893,12896,1290,12924,12958,12981,13005,1307,13088,13135,13216,13225,13252,13274,1345,13461,13484,13565,13635,13685,13692,13708,13718,1381,13855,13890,13938,13977,13991,14035,14041,1408,14085,14089,14104,14117,14125,14153,14158,1423,14250,14274,14307,14321,14331,14361,1438,14409,14450,14454,14490,1453,1454,1456,14573,14577,14593,1466,14676,14705,14724,14860,14864,14905,1491,14966,14979,14982,150,15043,15045,15058,15078,15089,15105,1512,15147,15152,15167,15170,15171,15189,15208,15212,15349,15412,15421,15444,1558,1567,15765,15774,15839,15876,15881,159,15909,15965,15970,16,16025,16049,16073,16078,16092,1611,16130,16151,16234,1624,16240,16285,16303,16314,16372,16394,16406,16431,1652,16551,16602,16616,16623,16674,1668,16692,1675,16779,16790,16836,16842,16845,16855,16858,16868,16876,16915,16924,16956,1696,16968,17141,17162,17185,1720,17241,17286,17333,17397,17418,17422,17424,17460,17466,17475,17537,17562,17592,17619,17625,17647,17717,1772,17720,17744,17801,1783,17883,17983,17989,1807,18107,18117,18173,18194,18202,18203,18236,18275,18300,18322,18345,18352,18383,18430,18461,1847,18507,18541,18566,18590,18697,18730,18741,18744,18786,18793,18833,18861,18883,18907,18942,18951,19043,19081,19099,19114,19162,19216,1923,19234,1925,19295,1933,1939,19396,19398,19428,19430,19436,19438,19473,19483,19493,19545,1955,19576,19578,19622,19637,1964,19650,1967,19673,19677,19710,1973,19742,19748,19798,19851,19858,19859,19870,19884,19895,19965,20017,20088,20093,20098,20133,20183,20213,20226,2025,20276,20286,20290,20314,20315,20316,20332,20341,20342,20407,20449,20476,20480,20513,2052,20520,20550,20561,20582,20627,20628,20631,20634,20684,20698,20716,20724,20727,20735,20745,208,20802,20808,2081,20829,20881,20888,20907,20909,20936,20981,20984,2102,21025,21040,21046,21060,21077,21086,2109,21112,21142,21180,21181,21195,21198,21213,21214,21234,21278,21280,21291,21338,21359,21371,21372,21403,21427,21431,21463,21464,21477,21484,21513,21517,21529,21533,21542,21548,21557,2158,21590,2161,2163,21644,21674,21685,21707,21730,21732,21735,21742,2175,21757,21836,22101,22166,224,22506,22560,22584,2264,22676,22688,22723,22748,22840,22901,22906,22916,22930,23,23076,23078,2312,23136,2319,23226,2325,23251,2329,23314,2338,2343],"n02676566":[10012,10017,1002,10062,10075,10100,10102,10114,10171,10299,1044,10467,1049,1056,10750,108,10830,10832,1087,10900,10902,10977,11034,11052,11067,11110,11113,11123,11129,11140,11184,11208,11215,1122,11239,11293,11301,11334,1136,11441,11513,11573,1158,11580,11643,118,11818,11897,11957,12007,12075,12103,12121,12164,12199,12229,12244,12344,1236,12360,12398,1240,12438,12503,1256,12565,12570,12573,12575,12643,12662,12691,12754,12763,12820,12827,12864,12910,12963,130,13036,1304,13067,13083,13126,13212,13267,13455,13473,13619,1365,13663,1393,1418,1434,1441,14507,15152,15190,15218,15296,1555,1587,1606,164,1645,1646,165,1651,167,1690,1691,17,1707,1728,1736,1757,178,1825,183,1835,1843,1855,1866,1882,1956,1964,1973,202,2046,2066,218,2187,219,22,2230,2237,2252,2256,2258,2263,2325,2330,2373,2380,242,2454,2460,2471,2480,2554,259,2631,2684,2696,2769,2778,2779,2803,281,2825,2876,2899,2913,2952,2954,2967,2970,2980,3004,306,3069,307,3085,3101,3109,3115,3120,3125,3127,3159,3161,3206,328,3314,3324,3325,3339,3340,3382,3385,3386,3394,3401,3402,341,3424,343,3440,3461,3471,3482,3492,3500,3523,3527,3528,3540,3541,3629,3650,3660,367,3750,3753,3767,380,3854,3876,3889,3892,3916,3920,3957,3977,3978,399,402,4057,4058,4059,406,4064,4074,4075,4095,4130,4134,4137,4160,4166,4171,4178,4208,4212,4236,4240,4241,4251,4252,4254,4305,4317,4343,4366,4391,4394,4402,4407,4415,4419,4425,4426,4441,4454,4462,4469,449,4493,4507,4530,4532,4537,4543,4573,4584,4595,4602,4604,4605,461,4621,4623,4632,4648,4668,467,4679,4697,4699,4706,4759,4789,4793,4795,4812,4823,4825,4839,4853,4877,4883,4893,4916,4928,4936,4946,5000,5001,5012,5109,511,5168,523,5255,5256,529,5293,5355,5356,5363,5364,5367,5372,5373,5380,5400,5408,5411,5412,5416,5421,5427,5430,5437,5438,5448,5450,5453,5454,5457,5460,5461,5464,5468,5476,5480,5489,5507,5508,5515,5518,5522,5524,5534,5535,5545,5559,556,5591,5601,5610,5637,5641,5645,5649,5684,5689,5713,5735,5742,5773,5776,5830,5860,5861,5871,5943,5966,602,6024,6030,6150,616,6178,6196,6200,6202,6234,6245,6253,6260,6265,6268,6269,6289,6292,6293,6302,6303,6304,6306,6319,6320,6326,6329,6335,6341,6343,6351,6354,6355,6363,6370,6372,6377,6378,6384,6390,6392,6402,6405,6414,6430,6435,6437,6466,6467,6469,6479,6489,6493,650,6518,6532,654,6544,6549,6554,6557,6559,6560,6563,6571,6575,6582,6583,6588,6589,6590,6604,6615,6625,6633,6636,6637,6644,6646,665,6658,6659,6660,6661,6672,6673,6674,6677,6682,6687,6703,6704,6705,6713,6737,6738,6744,6746,6750,6753,6762,6763,6764,6766,6770,6772,6778,6796,6799,6801,6839,684,6842,6843,6855,6857,6858,6865,6871,6875,6881],"n03272010":[10127,10170,10237,10363,10399,10424,10441,10443,10457,10510,106,10612,1067,10690,107,10795,10806,10815,10841,10848,10946,10973,11016,1109,11110,1114,11264,11276,11297,11343,11356,11378,11419,11443,11451,11498,115,11502,11505,11513,11538,11598,116,1165,11675,11740,11743,11764,11777,11814,11857,11879,11890,11925,11953,120,1203,12051,12069,12110,12151,12196,12204,1221,1242,12468,1259,1264,12662,1268,12924,1296,12967,1301,1304,13242,1328,1334,1336,1342,1345,1346,1377,13804,13813,13834,13914,13963,13968,1399,140,14010,1405,14073,1411,14119,14144,1415,142,1422,14226,1425,1432,14364,1442,1445,14490,14495,1462,1464,14690,14701,14736,1487,1496,1503,1507,15078,15107,1511,1523,1526,1529,1531,1536,1550,1551,1552,1553,1574,1576,15764,1579,15795,1582,15857,159,15907,15922,1594,1595,1608,1609,1616,1618,1630,1640,1643,1654,1656,1666,1676,16905,1699,17033,1708,1710,1711,1714,1726,1727,1739,1745,1747,175,1750,176,1760,1762,1763,17764,17768,1781,17811,1783,17873,1807,1852,1872,1881,189,1894,1924,1938,1939,1943,1953,1962,1973,1976,1988,1992,2001,2002,2018,2024,2033,2036,2042,2046,2058,2060,2070,2089,2114,2125,2126,2134,2137,2142,2150,2152,2159,2162,2168,2195,2197,2199,2210,2218,2223,2230,2232,2234,2238,2246,2256,2268,2278,2289,2294,2301,2312,2314,2316,232,2322,2323,2332,234,2372,2385,2387,2391,2411,2414,2420,2438,2442,2444,2459,2463,2467,2475,2481,2490,2529,2536,2559,2583,2618,263,2695,2772,278,2790,287,2880,2919,2922,2925,2929,2966,2999,3002,3022,3092,3136,3139,317,3181,3190,3191,3221,3227,3235,3239,3247,3251,3276,3281,3293,3294,330,3312,3313,3324,3338,3339,3343,3346,3353,3364,3371,3390,3393,34,3400,3414,3432,3452,3459,347,3471,3474,3475,3480,3482,3514,3521,3532,3538,3547,3552,3597,36,362,3621,364,3652,3669,3699,3715,3721,3726,3727,3728,373,3732,3743,3752,3763,3769,3770,3785,3790,3791,3793,3801,3805,3811,382,3832,386,3861,3870,3874,389,3896,3910,392,3929,3975,398,3981,3989,3993,3995,4005,4010,4016,4024,4053,4054,4061,4062,4075,4088,4092,4093,4099,412,4132,4135,4148,4199,4217,4218,4221,4229,4248,4252,4264,4287,431,433,4333,4340,4348,4361,4370,4372,4395,4441,4443,446,447,4472,4478,4509,4547,4553,4554,4572,4579,4587,459,4598,460,4607,4611,4616,4618,4621,4643,4648,4660,4669,4673,4693,4702,4703,4713,4726,4728,474,4751,4766,4768,4769,477,4774,4776,4778,4800,4806,4818,4821,4829,4830,4832,4836,4837,4845,4858,4861,4864,4865,4872,4876,4886,49,4900,4901,4902,491,4939,4950,4953,4966,5019,504,5103,5150,5152,5177,519,52,525,5253,5257,526,527,534,5362,5373,5381,5412,5421,5424,5425,5427,543,5437,544,549,5510,5514,552,5525],"n04152593":[10032,10033,1011,10111,10136,10240,10293,10382,1039,10409,10413,1052,10559,10572,10684,10707,10729,10778,10860,10874,10988,11016,11054,11079,11094,11120,11193,1122,11226,11247,11444,11495,1150,11516,11537,11572,11659,11730,11747,11804,11848,11876,11898,11901,11903,11945,12111,12136,1214,12184,12204,12249,12276,12299,12324,1244,12450,12470,12539,12547,1258,12581,12607,12653,12665,127,12727,12759,12771,1290,12912,12948,12952,12961,12975,12992,12996,13020,13026,13031,13082,13116,13122,13127,13168,13170,13181,13203,13206,1324,13244,13319,13328,13347,13402,13430,135,13511,13576,13603,13640,13665,13698,1375,13774,13802,13815,13819,13826,13834,13922,13962,13975,13979,13988,13993,14043,14058,14115,14228,14328,14343,14420,14440,14444,14474,145,14525,14537,14547,14553,14567,14576,14590,14638,14650,14665,14733,1474,14741,14776,14811,14818,14828,1483,14844,1485,14850,14890,1493,15007,15045,1507,15090,15129,15140,15212,15220,15235,15291,1533,15359,1543,15479,15517,15522,15533,1554,15547,15551,15569,1558,15696,15712,15748,15752,15767,15810,15879,15891,15954,16019,16096,16142,16189,16200,16264,1629,16323,16328,16329,16344,16363,16367,16370,16381,16391,16393,16416,16457,1646,16466,16478,16488,16515,16592,16627,16646,16690,16714,16718,1679,16819,16822,16846,16856,16866,16931,16966,16989,16990,16993,1706,17111,1712,1716,17181,17206,17233,17258,17329,17460,17469,17494,17512,17535,17629,17647,17767,17789,178,1782,17967,18034,18116,182,18217,18219,18233,18331,18361,18442,18451,185,18503,18540,18552,18557,18567,18570,18577,18585,18593,18611,18627,1863,18633,18643,18656,18674,18689,18698,18706,18708,1875,18781,18911,18921,18923,190,19066,19096,19194,19493,19553,19555,19604,1962,19716,19771,19779,19889,19923,1995,1997,20054,20092,2018,20281,2037,20371,20393,20412,20439,2051,20542,20554,2063,20696,20787,20874,20960,20962,21046,21134,21199,21202,21232,21480,2152,2153,21607,21732,2174,21772,2193,21966,22077,22166,22231,2226,22299,22306,22414,22421,22451,22460,22559,22609,22613,22642,22652,22675,22742,2283,23051,23055,2320,23232,23324,23354,23492,23503,23504,23528,23531,23540,23560,23627,23765,23836,23842,23888,23892,23952,24004,24028,24045,24069,24127,24223,24235,24260,24319,2441,24465,24508,24510,24566,24567,2457,24576,24651,2466,24697,2479,24839,24856,24901,25052,25066,25071,25131,25202,25203,25334,25368,2560,25630,25673,2568,25707,25837,26118,26146,26280,26390,2641,26479,26847,26887,26950,26980,26991,2712,2718,27273,27511,27530,27579,27590,2761,27710,2777,27847,27876,27895,27926,2793,27975,27986,28060,28105,28288,28312,2833,2841,28468,2858,28607,28626,28675,28760,288,28862,2889,28913,29083,29091,29097,29342,29410,29477,29489,29558,29593,29607,29609,2969,29774,29818,29859,2986,29990,3003,30034,30138,30172,30233,30380,30465,30487,30577,30630,3070,30743,30766,3085,30864,30909,3097,31026,31103,3116,31225,31304,3134,31376,3140,31429,31451,31503,31636,3164,3165,31822,31846,31938,31988,32010],"n04590129":[1001,10036,1006,1014,10201,10378,10412,1049,10499,10522,10550,10565,10597,10680,1069,10691,10697,10726,10734,10735,10736,10746,10756,10783,10862,10864,10881,10920,10940,11028,11034,11098,11107,1112,1113,11155,11157,1116,11167,1118,11265,11362,11388,1143,11484,11510,11535,1155,1159,1162,1167,11673,11706,1173,11820,11844,11845,1186,1194,11991,1215,1221,1223,12234,12238,1227,12321,12349,1239,12475,12548,12596,12649,1272,12733,12762,12774,12799,12862,12877,129,1290,12966,13042,13080,13289,1330,13365,13375,13399,13429,13434,13452,13501,13521,13577,13767,13799,13840,13918,13993,140,1407,14074,14136,14154,142,14261,1429,14372,14415,14465,14489,14611,1480,14803,14815,1494,14945,14978,14995,14997,1502,15112,15194,15209,1534,15342,1536,15442,15483,15533,1559,15622,15675,15800,15806,15822,15827,15830,15852,15867,15904,1592,1595,16084,16104,1613,16131,16167,16175,16218,1623,16231,16242,16331,16350,16357,16386,1640,16410,16427,16547,16600,1663,16683,1671,16759,16760,16763,16766,16788,16793,1680,16801,16815,16819,16839,16873,16886,1695,16974,1698,16998,17044,1706,17067,17086,17134,17145,17147,17174,1720,17237,1728,17300,17309,17311,17335,17340,17347,17387,17388,17392,17425,17431,17445,17464,17487,1749,17521,17526,17533,17606,1764,17648,17654,17661,17665,1767,17680,17681,17685,17692,17725,17729,17749,1778,1781,17833,1785,17897,1790,17925,1796,17996,18016,18024,18027,18046,18070,1812,18120,1816,18176,18209,18220,18333,18346,18351,18381,18415,18437,18448,18495,18506,18575,18637,1866,1869,18732,18751,188,1885,189,18927,18950,19100,19114,19117,19213,19295,19428,19499,1955,1968,19722,19727,19790,19795,19866,1993,1994,19977,1999,20,20009,20081,2009,20119,2012,20164,20228,20243,20259,20267,20274,2028,2030,20346,20384,2039,20396,20438,20487,20597,2060,20641,20667,20691,20719,20758,20763,20808,20811,2082,2086,2089,20897,20900,20919,20925,2094,20960,20983,2102,21054,21094,21115,2112,21124,21137,21140,21151,21174,21176,21190,21299,21318,21362,21377,21379,21396,21408,21418,21436,21466,21467,21479,21481,21492,21494,21511,21523,21542,21559,21585,21601,21608,21613,21618,21621,21631,21636,21642,21662,21689,21697,21736,21763,21804,21811,21827,21838,21839,21841,21858,21890,21891,21892,21908,21916,21938,2195,21963,21968,21976,21980,21998,22042,22048,22049,2206,22064,22068,22078,22110,22128,22135,22149,22164,22175,22194,22205,22207,22210,2223,22238,22241,22252,22263,22277,22281,22292,22294,22295,22307,22389,22407,22408,2243,22436,22476,22520,22524,22565,22597,22604,22609,22641,22645,22685,22728,22785,2280,2281,22812,2283,22843,22854,22879,22883,22891,22892,22943,2298,2302,23022,23023,23033,23058,23085,23131,23152,23180,23181,23247,23323,23324,23326,23336,23375,23379,23389,23441,23469,235,23505,23525,23533,23548,2355,23593,23600,23620,2365,23665,23719,23721,2373,23750,2376,23779,23894,23931,23966,23972,24008,24026,24046,24048,24073,24116,24117,24171,24183,24195,24231,24260,24300],"n04418357":[10012,10109,10121,10136,10249,1026,10293,1030,10333,10404,10416,10475,10487,1052,10530,10559,1056,10714,10779,10813,1082,10924,10949,10955,10989,1101,11025,11042,11115,11146,11151,11304,11361,11371,11391,11409,11425,11432,11485,1151,11526,11529,1156,11591,11635,11672,11674,11779,11850,11863,11962,1198,11980,11981,11998,12073,12088,1209,121,12219,12231,12233,12270,12288,12292,12317,12353,12397,12430,12439,12458,12508,12513,12572,12581,12635,12672,12703,12708,12728,12854,1289,12932,1300,13018,13035,13041,13043,13140,13203,13208,13364,13378,13381,13513,13527,13597,13614,1368,1372,13785,13793,1380,13920,140,1402,14107,14160,14214,14229,1426,14562,14607,14610,14622,14707,14791,14792,148,14906,14942,14988,15132,1529,1530,15350,1539,15509,15519,1552,15521,15532,15553,15577,15601,15635,15663,1576,15830,15884,1589,15897,1592,1597,15980,16041,16091,16096,161,16112,16163,1617,16203,16387,16470,16500,16508,16558,16589,16592,16632,16656,16666,16760,16761,16780,16809,1683,16839,16881,1693,16944,171,17118,17176,17216,1723,17243,17261,17312,17320,17341,17401,1741,1744,17476,17506,17518,1752,17556,1757,17594,17807,1782,17851,17862,17940,17967,18001,18018,1802,1810,18152,18160,18174,18215,18235,18284,18301,184,18402,18419,18438,18449,185,18538,18551,1861,18740,1876,18828,18848,1892,1897,18982,18995,19010,19022,1904,19106,19144,19178,19237,19239,1927,19390,19447,1947,19540,19642,19651,19660,19698,19704,19709,19772,19824,19852,19865,19866,19947,2007,20124,20149,20153,20241,20248,20313,2032,20326,20345,20379,2046,20521,20522,2053,20674,2073,2085,20863,20920,20978,20984,20997,2105,21069,21070,2143,21456,2146,21478,2153,21539,21633,21639,21661,2170,21787,21822,21826,21890,2190,21967,21978,21982,21997,22007,22021,22056,22097,221,22281,22357,22462,22465,2248,22486,22593,22654,22677,22691,22767,22927,2293,2295,22963,22967,23052,23053,2317,232,23266,23268,2340,23464,23471,2350,23516,23531,23567,23581,2366,23675,2372,23738,23757,2387,2393,24,24284,24370,24434,24446,2445,24513,24638,2471,24752,2476,24841,2486,2517,25255,2531,25363,25364,2545,2550,2551,25554,25661,25667,25699,25708,25739,25743,25877,2590,25910,25962,25982,26000,26036,26078,26340,2642,26423,26639,26695,2673,26765,26841,26867,26950,2714,27198,2728,2754,2758,2759,2763,27705,27761,27827,27830,27869,27934,27939,27940,28021,28058,28105,28157,28164,28166,28205,28432,2844,28465,2849,28531,28532,2856,28571,28670,28702,28750,28793,2893,28931,28971,28983,2900,29018,29038,2904,29191,2925,29371,2940,29431,29463,2955,29560,29576,29729,29804,29874,29914,2997,30043,3005,30144,30258,30285,3034,3044,30546,3056,30581,30634,30646,3070,30732,30860,30938,31130,31292,3136,3145,31484,3150,31543,3156,3161,3165,31708,31858,3188,31936,3209,32097,32176,32187,3225,32472,32492,32639,3273,32731,3278,3316,3318,332,3342,3371,3385,33970,34,34096,34097,34211,3437,34444,3459,3467,34764,3488,3518,35201]}}
discovery/prop_specificity.json ADDED
@@ -0,0 +1,710 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "provenance": {
3
+ "generator": null,
4
+ "note": "discovery sweep not committed; no producer in the repo"
5
+ },
6
+ "n_prop_images": 8479,
7
+ "ranked_candidates": [
8
+ {
9
+ "dim": 11,
10
+ "prop_mean": 1.7091748714447021,
11
+ "person_pos_mean": 1.089888095855713,
12
+ "person_neg_mean": 1.1654655933380127,
13
+ "prop_vs_neg": 0.5437092781066895,
14
+ "person_vs_neg": -0.0755774974822998,
15
+ "score": 0.6192867755889893
16
+ },
17
+ {
18
+ "dim": 697,
19
+ "prop_mean": 1.63211190700531,
20
+ "person_pos_mean": 1.0918049812316895,
21
+ "person_neg_mean": 1.0371267795562744,
22
+ "prop_vs_neg": 0.5949851274490356,
23
+ "person_vs_neg": 0.05467820167541504,
24
+ "score": 0.5403069257736206
25
+ },
26
+ {
27
+ "dim": 572,
28
+ "prop_mean": 1.4398308992385864,
29
+ "person_pos_mean": 0.9548083543777466,
30
+ "person_neg_mean": 0.8750605583190918,
31
+ "prop_vs_neg": 0.5647703409194946,
32
+ "person_vs_neg": 0.07974779605865479,
33
+ "score": 0.48502254486083984
34
+ },
35
+ {
36
+ "dim": 671,
37
+ "prop_mean": 1.5082179307937622,
38
+ "person_pos_mean": 1.0776139497756958,
39
+ "person_neg_mean": 1.0892691612243652,
40
+ "prop_vs_neg": 0.418948769569397,
41
+ "person_vs_neg": -0.011655211448669434,
42
+ "score": 0.4306039810180664
43
+ },
44
+ {
45
+ "dim": 51,
46
+ "prop_mean": 1.2916672229766846,
47
+ "person_pos_mean": 0.8612672090530396,
48
+ "person_neg_mean": 0.6743829846382141,
49
+ "prop_vs_neg": 0.6172842383384705,
50
+ "person_vs_neg": 0.18688422441482544,
51
+ "score": 0.430400013923645
52
+ },
53
+ {
54
+ "dim": 186,
55
+ "prop_mean": 1.783549189567566,
56
+ "person_pos_mean": 1.3718806505203247,
57
+ "person_neg_mean": 1.2572942972183228,
58
+ "prop_vs_neg": 0.5262548923492432,
59
+ "person_vs_neg": 0.11458635330200195,
60
+ "score": 0.4116685390472412
61
+ },
62
+ {
63
+ "dim": 752,
64
+ "prop_mean": 1.4307368993759155,
65
+ "person_pos_mean": 1.0203551054000854,
66
+ "person_neg_mean": 0.9466467499732971,
67
+ "prop_vs_neg": 0.4840901494026184,
68
+ "person_vs_neg": 0.07370835542678833,
69
+ "score": 0.4103817939758301
70
+ },
71
+ {
72
+ "dim": 90,
73
+ "prop_mean": 1.4134135246276855,
74
+ "person_pos_mean": 1.027899980545044,
75
+ "person_neg_mean": 1.2588216066360474,
76
+ "prop_vs_neg": 0.15459191799163818,
77
+ "person_vs_neg": -0.23092162609100342,
78
+ "score": 0.3855135440826416
79
+ },
80
+ {
81
+ "dim": 161,
82
+ "prop_mean": 1.110310673713684,
83
+ "person_pos_mean": 0.7282975316047668,
84
+ "person_neg_mean": 0.8111143708229065,
85
+ "prop_vs_neg": 0.2991963028907776,
86
+ "person_vs_neg": -0.08281683921813965,
87
+ "score": 0.38201314210891724
88
+ },
89
+ {
90
+ "dim": 310,
91
+ "prop_mean": 1.6827064752578735,
92
+ "person_pos_mean": 1.306183934211731,
93
+ "person_neg_mean": 1.5448638200759888,
94
+ "prop_vs_neg": 0.13784265518188477,
95
+ "person_vs_neg": -0.2386798858642578,
96
+ "score": 0.3765225410461426
97
+ },
98
+ {
99
+ "dim": 88,
100
+ "prop_mean": 1.7690833806991577,
101
+ "person_pos_mean": 1.3999791145324707,
102
+ "person_neg_mean": 1.0576374530792236,
103
+ "prop_vs_neg": 0.7114459276199341,
104
+ "person_vs_neg": 0.34234166145324707,
105
+ "score": 0.369104266166687
106
+ },
107
+ {
108
+ "dim": 189,
109
+ "prop_mean": 1.2615772485733032,
110
+ "person_pos_mean": 0.893500030040741,
111
+ "person_neg_mean": 1.0122331380844116,
112
+ "prop_vs_neg": 0.2493441104888916,
113
+ "person_vs_neg": -0.11873310804367065,
114
+ "score": 0.36807721853256226
115
+ },
116
+ {
117
+ "dim": 613,
118
+ "prop_mean": 1.854291558265686,
119
+ "person_pos_mean": 1.4975982904434204,
120
+ "person_neg_mean": 1.73020339012146,
121
+ "prop_vs_neg": 0.12408816814422607,
122
+ "person_vs_neg": -0.23260509967803955,
123
+ "score": 0.3566932678222656
124
+ },
125
+ {
126
+ "dim": 201,
127
+ "prop_mean": 1.4382827281951904,
128
+ "person_pos_mean": 1.0972189903259277,
129
+ "person_neg_mean": 1.0087069272994995,
130
+ "prop_vs_neg": 0.4295758008956909,
131
+ "person_vs_neg": 0.08851206302642822,
132
+ "score": 0.3410637378692627
133
+ },
134
+ {
135
+ "dim": 360,
136
+ "prop_mean": 1.5004557371139526,
137
+ "person_pos_mean": 1.1636990308761597,
138
+ "person_neg_mean": 1.24490487575531,
139
+ "prop_vs_neg": 0.2555508613586426,
140
+ "person_vs_neg": -0.08120584487915039,
141
+ "score": 0.33675670623779297
142
+ },
143
+ {
144
+ "dim": 623,
145
+ "prop_mean": 1.1354831457138062,
146
+ "person_pos_mean": 0.8059989809989929,
147
+ "person_neg_mean": 0.7583788633346558,
148
+ "prop_vs_neg": 0.3771042823791504,
149
+ "person_vs_neg": 0.04762011766433716,
150
+ "score": 0.32948416471481323
151
+ },
152
+ {
153
+ "dim": 688,
154
+ "prop_mean": 1.4130481481552124,
155
+ "person_pos_mean": 1.0897259712219238,
156
+ "person_neg_mean": 1.1911897659301758,
157
+ "prop_vs_neg": 0.22185838222503662,
158
+ "person_vs_neg": -0.10146379470825195,
159
+ "score": 0.3233221769332886
160
+ },
161
+ {
162
+ "dim": 578,
163
+ "prop_mean": 1.6479562520980835,
164
+ "person_pos_mean": 1.3283374309539795,
165
+ "person_neg_mean": 1.4025310277938843,
166
+ "prop_vs_neg": 0.24542522430419922,
167
+ "person_vs_neg": -0.07419359683990479,
168
+ "score": 0.319618821144104
169
+ },
170
+ {
171
+ "dim": 81,
172
+ "prop_mean": 1.904716968536377,
173
+ "person_pos_mean": 1.597719430923462,
174
+ "person_neg_mean": 1.7867015600204468,
175
+ "prop_vs_neg": 0.11801540851593018,
176
+ "person_vs_neg": -0.18898212909698486,
177
+ "score": 0.30699753761291504
178
+ },
179
+ {
180
+ "dim": 692,
181
+ "prop_mean": 1.381159782409668,
182
+ "person_pos_mean": 1.0755085945129395,
183
+ "person_neg_mean": 1.1753791570663452,
184
+ "prop_vs_neg": 0.20578062534332275,
185
+ "person_vs_neg": -0.09987056255340576,
186
+ "score": 0.3056511878967285
187
+ },
188
+ {
189
+ "dim": 638,
190
+ "prop_mean": 1.2797389030456543,
191
+ "person_pos_mean": 0.9781198501586914,
192
+ "person_neg_mean": 1.0837996006011963,
193
+ "prop_vs_neg": 0.195939302444458,
194
+ "person_vs_neg": -0.10567975044250488,
195
+ "score": 0.3016190528869629
196
+ },
197
+ {
198
+ "dim": 522,
199
+ "prop_mean": 1.194052815437317,
200
+ "person_pos_mean": 0.8986038565635681,
201
+ "person_neg_mean": 0.8541845679283142,
202
+ "prop_vs_neg": 0.3398682475090027,
203
+ "person_vs_neg": 0.044419288635253906,
204
+ "score": 0.2954489588737488
205
+ },
206
+ {
207
+ "dim": 257,
208
+ "prop_mean": 1.8521565198898315,
209
+ "person_pos_mean": 1.5771883726119995,
210
+ "person_neg_mean": 1.6870157718658447,
211
+ "prop_vs_neg": 0.16514074802398682,
212
+ "person_vs_neg": -0.10982739925384521,
213
+ "score": 0.27496814727783203
214
+ },
215
+ {
216
+ "dim": 340,
217
+ "prop_mean": 0.9343926906585693,
218
+ "person_pos_mean": 0.6695823669433594,
219
+ "person_neg_mean": 0.6695160269737244,
220
+ "prop_vs_neg": 0.26487666368484497,
221
+ "person_vs_neg": 6.633996963500977e-05,
222
+ "score": 0.26481032371520996
223
+ },
224
+ {
225
+ "dim": 308,
226
+ "prop_mean": 1.525191068649292,
227
+ "person_pos_mean": 1.2669845819473267,
228
+ "person_neg_mean": 1.1947391033172607,
229
+ "prop_vs_neg": 0.33045196533203125,
230
+ "person_vs_neg": 0.07224547863006592,
231
+ "score": 0.25820648670196533
232
+ },
233
+ {
234
+ "dim": 479,
235
+ "prop_mean": 1.2297685146331787,
236
+ "person_pos_mean": 0.9748978018760681,
237
+ "person_neg_mean": 0.9749308824539185,
238
+ "prop_vs_neg": 0.25483763217926025,
239
+ "person_vs_neg": -3.30805778503418e-05,
240
+ "score": 0.2548707127571106
241
+ },
242
+ {
243
+ "dim": 299,
244
+ "prop_mean": 1.3217650651931763,
245
+ "person_pos_mean": 1.0688191652297974,
246
+ "person_neg_mean": 1.1240233182907104,
247
+ "prop_vs_neg": 0.19774174690246582,
248
+ "person_vs_neg": -0.055204153060913086,
249
+ "score": 0.2529458999633789
250
+ },
251
+ {
252
+ "dim": 440,
253
+ "prop_mean": 1.1131365299224854,
254
+ "person_pos_mean": 0.8602599501609802,
255
+ "person_neg_mean": 0.8018559217453003,
256
+ "prop_vs_neg": 0.31128060817718506,
257
+ "person_vs_neg": 0.05840402841567993,
258
+ "score": 0.2528765797615051
259
+ },
260
+ {
261
+ "dim": 458,
262
+ "prop_mean": 1.2792733907699585,
263
+ "person_pos_mean": 1.029525637626648,
264
+ "person_neg_mean": 0.9404508471488953,
265
+ "prop_vs_neg": 0.33882254362106323,
266
+ "person_vs_neg": 0.08907479047775269,
267
+ "score": 0.24974775314331055
268
+ },
269
+ {
270
+ "dim": 767,
271
+ "prop_mean": 1.4785064458847046,
272
+ "person_pos_mean": 1.2433205842971802,
273
+ "person_neg_mean": 1.2814769744873047,
274
+ "prop_vs_neg": 0.1970294713973999,
275
+ "person_vs_neg": -0.03815639019012451,
276
+ "score": 0.23518586158752441
277
+ },
278
+ {
279
+ "dim": 713,
280
+ "prop_mean": 2.0065953731536865,
281
+ "person_pos_mean": 1.7764008045196533,
282
+ "person_neg_mean": 1.8709570169448853,
283
+ "prop_vs_neg": 0.13563835620880127,
284
+ "person_vs_neg": -0.09455621242523193,
285
+ "score": 0.2301945686340332
286
+ },
287
+ {
288
+ "dim": 554,
289
+ "prop_mean": 1.3773629665374756,
290
+ "person_pos_mean": 1.1489109992980957,
291
+ "person_neg_mean": 1.0569530725479126,
292
+ "prop_vs_neg": 0.320409893989563,
293
+ "person_vs_neg": 0.0919579267501831,
294
+ "score": 0.22845196723937988
295
+ },
296
+ {
297
+ "dim": 0,
298
+ "prop_mean": 1.518669605255127,
299
+ "person_pos_mean": 1.292944312095642,
300
+ "person_neg_mean": 1.2706265449523926,
301
+ "prop_vs_neg": 0.24804306030273438,
302
+ "person_vs_neg": 0.02231776714324951,
303
+ "score": 0.22572529315948486
304
+ },
305
+ {
306
+ "dim": 351,
307
+ "prop_mean": 1.5037388801574707,
308
+ "person_pos_mean": 1.2783546447753906,
309
+ "person_neg_mean": 1.0354632139205933,
310
+ "prop_vs_neg": 0.46827566623687744,
311
+ "person_vs_neg": 0.24289143085479736,
312
+ "score": 0.22538423538208008
313
+ },
314
+ {
315
+ "dim": 266,
316
+ "prop_mean": 3.2978885173797607,
317
+ "person_pos_mean": 3.0799922943115234,
318
+ "person_neg_mean": 3.1888484954833984,
319
+ "prop_vs_neg": 0.1090400218963623,
320
+ "person_vs_neg": -0.108856201171875,
321
+ "score": 0.2178962230682373
322
+ },
323
+ {
324
+ "dim": 469,
325
+ "prop_mean": 1.2845133543014526,
326
+ "person_pos_mean": 1.0693851709365845,
327
+ "person_neg_mean": 1.0766862630844116,
328
+ "prop_vs_neg": 0.20782709121704102,
329
+ "person_vs_neg": -0.0073010921478271484,
330
+ "score": 0.21512818336486816
331
+ },
332
+ {
333
+ "dim": 250,
334
+ "prop_mean": 1.1550997495651245,
335
+ "person_pos_mean": 0.9470953941345215,
336
+ "person_neg_mean": 1.0209672451019287,
337
+ "prop_vs_neg": 0.1341325044631958,
338
+ "person_vs_neg": -0.07387185096740723,
339
+ "score": 0.20800435543060303
340
+ },
341
+ {
342
+ "dim": 684,
343
+ "prop_mean": 1.217105507850647,
344
+ "person_pos_mean": 1.0223654508590698,
345
+ "person_neg_mean": 0.9934371113777161,
346
+ "prop_vs_neg": 0.2236683964729309,
347
+ "person_vs_neg": 0.02892833948135376,
348
+ "score": 0.19474005699157715
349
+ },
350
+ {
351
+ "dim": 604,
352
+ "prop_mean": 1.4056569337844849,
353
+ "person_pos_mean": 1.2120150327682495,
354
+ "person_neg_mean": 1.3198471069335938,
355
+ "prop_vs_neg": 0.08580982685089111,
356
+ "person_vs_neg": -0.10783207416534424,
357
+ "score": 0.19364190101623535
358
+ },
359
+ {
360
+ "dim": 251,
361
+ "prop_mean": 1.238474726676941,
362
+ "person_pos_mean": 1.0456016063690186,
363
+ "person_neg_mean": 1.1859287023544312,
364
+ "prop_vs_neg": 0.052546024322509766,
365
+ "person_vs_neg": -0.1403270959854126,
366
+ "score": 0.19287312030792236
367
+ },
368
+ {
369
+ "dim": 275,
370
+ "prop_mean": 1.1440684795379639,
371
+ "person_pos_mean": 0.9552448391914368,
372
+ "person_neg_mean": 1.0024899244308472,
373
+ "prop_vs_neg": 0.1415785551071167,
374
+ "person_vs_neg": -0.0472450852394104,
375
+ "score": 0.1888236403465271
376
+ },
377
+ {
378
+ "dim": 236,
379
+ "prop_mean": 1.3713657855987549,
380
+ "person_pos_mean": 1.1866233348846436,
381
+ "person_neg_mean": 1.346700668334961,
382
+ "prop_vs_neg": 0.024665117263793945,
383
+ "person_vs_neg": -0.16007733345031738,
384
+ "score": 0.18474245071411133
385
+ },
386
+ {
387
+ "dim": 110,
388
+ "prop_mean": 1.3540358543395996,
389
+ "person_pos_mean": 1.1696140766143799,
390
+ "person_neg_mean": 1.1514066457748413,
391
+ "prop_vs_neg": 0.2026292085647583,
392
+ "person_vs_neg": 0.018207430839538574,
393
+ "score": 0.18442177772521973
394
+ },
395
+ {
396
+ "dim": 660,
397
+ "prop_mean": 1.294521689414978,
398
+ "person_pos_mean": 1.110160231590271,
399
+ "person_neg_mean": 1.258522391319275,
400
+ "prop_vs_neg": 0.035999298095703125,
401
+ "person_vs_neg": -0.1483621597290039,
402
+ "score": 0.18436145782470703
403
+ },
404
+ {
405
+ "dim": 141,
406
+ "prop_mean": 1.2327960729599,
407
+ "person_pos_mean": 1.0510427951812744,
408
+ "person_neg_mean": 1.1455810070037842,
409
+ "prop_vs_neg": 0.08721506595611572,
410
+ "person_vs_neg": -0.09453821182250977,
411
+ "score": 0.1817532777786255
412
+ },
413
+ {
414
+ "dim": 436,
415
+ "prop_mean": 0.7790431380271912,
416
+ "person_pos_mean": 0.6001676321029663,
417
+ "person_neg_mean": 0.7653905153274536,
418
+ "prop_vs_neg": 0.013652622699737549,
419
+ "person_vs_neg": -0.1652228832244873,
420
+ "score": 0.17887550592422485
421
+ },
422
+ {
423
+ "dim": 732,
424
+ "prop_mean": 2.213838815689087,
425
+ "person_pos_mean": 2.035112142562866,
426
+ "person_neg_mean": 2.0301244258880615,
427
+ "prop_vs_neg": 0.1837143898010254,
428
+ "person_vs_neg": 0.0049877166748046875,
429
+ "score": 0.1787266731262207
430
+ },
431
+ {
432
+ "dim": 160,
433
+ "prop_mean": 1.1959549188613892,
434
+ "person_pos_mean": 1.02139151096344,
435
+ "person_neg_mean": 0.985637366771698,
436
+ "prop_vs_neg": 0.21031755208969116,
437
+ "person_vs_neg": 0.03575414419174194,
438
+ "score": 0.17456340789794922
439
+ },
440
+ {
441
+ "dim": 543,
442
+ "prop_mean": 1.6499507427215576,
443
+ "person_pos_mean": 1.4781776666641235,
444
+ "person_neg_mean": 1.5063472986221313,
445
+ "prop_vs_neg": 0.14360344409942627,
446
+ "person_vs_neg": -0.028169631958007812,
447
+ "score": 0.17177307605743408
448
+ },
449
+ {
450
+ "dim": 27,
451
+ "prop_mean": 1.3894929885864258,
452
+ "person_pos_mean": 1.2205332517623901,
453
+ "person_neg_mean": 1.2239311933517456,
454
+ "prop_vs_neg": 0.16556179523468018,
455
+ "person_vs_neg": -0.0033979415893554688,
456
+ "score": 0.16895973682403564
457
+ }
458
+ ],
459
+ "sweeps": [
460
+ {
461
+ "extra_neg_k": 0,
462
+ "F1": 0.884742021560669,
463
+ "threshold": 24.868803024291992,
464
+ "precision": 0.8963522911071777,
465
+ "recall": 0.8734287023544312,
466
+ "prop_false_positive_rate": 0.0593230314552784,
467
+ "added_dims": []
468
+ },
469
+ {
470
+ "extra_neg_k": 5,
471
+ "F1": 0.8799648880958557,
472
+ "threshold": 19.580781936645508,
473
+ "precision": 0.8911111354827881,
474
+ "recall": 0.8690940737724304,
475
+ "prop_false_positive_rate": 0.04576011374592781,
476
+ "added_dims": [
477
+ 11,
478
+ 697,
479
+ 572,
480
+ 671,
481
+ 51
482
+ ]
483
+ },
484
+ {
485
+ "extra_neg_k": 10,
486
+ "F1": 0.8703535795211792,
487
+ "threshold": 15.480006217956543,
488
+ "precision": 0.9121140241622925,
489
+ "recall": 0.8322497010231018,
490
+ "prop_false_positive_rate": 0.033730391412973404,
491
+ "added_dims": [
492
+ 11,
493
+ 697,
494
+ 572,
495
+ 671,
496
+ 51,
497
+ 186,
498
+ 752,
499
+ 90,
500
+ 161,
501
+ 310
502
+ ]
503
+ },
504
+ {
505
+ "extra_neg_k": 15,
506
+ "F1": 0.8526602983474731,
507
+ "threshold": 10.094175338745117,
508
+ "precision": 0.8967001438140869,
509
+ "recall": 0.8127438426017761,
510
+ "prop_false_positive_rate": 0.027243778109550476,
511
+ "added_dims": [
512
+ 11,
513
+ 697,
514
+ 572,
515
+ 671,
516
+ 51,
517
+ 186,
518
+ 752,
519
+ 90,
520
+ 161,
521
+ 310,
522
+ 88,
523
+ 189,
524
+ 613,
525
+ 201,
526
+ 360
527
+ ]
528
+ },
529
+ {
530
+ "extra_neg_k": 20,
531
+ "F1": 0.8446601629257202,
532
+ "threshold": 2.4011502265930176,
533
+ "precision": 0.860224723815918,
534
+ "recall": 0.8296489119529724,
535
+ "prop_false_positive_rate": 0.031135747209191322,
536
+ "added_dims": [
537
+ 11,
538
+ 697,
539
+ 572,
540
+ 671,
541
+ 51,
542
+ 186,
543
+ 752,
544
+ 90,
545
+ 161,
546
+ 310,
547
+ 88,
548
+ 189,
549
+ 613,
550
+ 201,
551
+ 360,
552
+ 623,
553
+ 688,
554
+ 578,
555
+ 81,
556
+ 692
557
+ ]
558
+ },
559
+ {
560
+ "extra_neg_k": 30,
561
+ "F1": 0.8187528252601624,
562
+ "threshold": -6.868839740753174,
563
+ "precision": 0.8484426140785217,
564
+ "recall": 0.7910706400871277,
565
+ "prop_false_positive_rate": 0.023115932941436768,
566
+ "added_dims": [
567
+ 11,
568
+ 697,
569
+ 572,
570
+ 671,
571
+ 51,
572
+ 186,
573
+ 752,
574
+ 90,
575
+ 161,
576
+ 310,
577
+ 88,
578
+ 189,
579
+ 613,
580
+ 201,
581
+ 360,
582
+ 623,
583
+ 688,
584
+ 578,
585
+ 81,
586
+ 692,
587
+ 638,
588
+ 522,
589
+ 257,
590
+ 340,
591
+ 308,
592
+ 479,
593
+ 299,
594
+ 440,
595
+ 458,
596
+ 767
597
+ ]
598
+ },
599
+ {
600
+ "extra_neg_k": 40,
601
+ "F1": 0.7954637408256531,
602
+ "threshold": -25.408130645751953,
603
+ "precision": 0.7464842200279236,
604
+ "recall": 0.8513220548629761,
605
+ "prop_false_positive_rate": 0.043047528713941574,
606
+ "added_dims": [
607
+ 11,
608
+ 697,
609
+ 572,
610
+ 671,
611
+ 51,
612
+ 186,
613
+ 752,
614
+ 90,
615
+ 161,
616
+ 310,
617
+ 88,
618
+ 189,
619
+ 613,
620
+ 201,
621
+ 360,
622
+ 623,
623
+ 688,
624
+ 578,
625
+ 81,
626
+ 692,
627
+ 638,
628
+ 522,
629
+ 257,
630
+ 340,
631
+ 308,
632
+ 479,
633
+ 299,
634
+ 440,
635
+ 458,
636
+ 767,
637
+ 713,
638
+ 554,
639
+ 0,
640
+ 351,
641
+ 266,
642
+ 469,
643
+ 250,
644
+ 684,
645
+ 604,
646
+ 251
647
+ ]
648
+ },
649
+ {
650
+ "extra_neg_k": 50,
651
+ "F1": 0.7818812131881714,
652
+ "threshold": -36.89189910888672,
653
+ "precision": 0.7386276125907898,
654
+ "recall": 0.8305158019065857,
655
+ "prop_false_positive_rate": 0.041278451681137085,
656
+ "added_dims": [
657
+ 11,
658
+ 697,
659
+ 572,
660
+ 671,
661
+ 51,
662
+ 186,
663
+ 752,
664
+ 90,
665
+ 161,
666
+ 310,
667
+ 88,
668
+ 189,
669
+ 613,
670
+ 201,
671
+ 360,
672
+ 623,
673
+ 688,
674
+ 578,
675
+ 81,
676
+ 692,
677
+ 638,
678
+ 522,
679
+ 257,
680
+ 340,
681
+ 308,
682
+ 479,
683
+ 299,
684
+ 440,
685
+ 458,
686
+ 767,
687
+ 713,
688
+ 554,
689
+ 0,
690
+ 351,
691
+ 266,
692
+ 469,
693
+ 250,
694
+ 684,
695
+ 604,
696
+ 251,
697
+ 275,
698
+ 236,
699
+ 110,
700
+ 660,
701
+ 141,
702
+ 436,
703
+ 732,
704
+ 160,
705
+ 543,
706
+ 27
707
+ ]
708
+ }
709
+ ]
710
+ }
discovery/variant_leaderboard.json ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "provenance": {
3
+ "generator": null,
4
+ "note": "discovery sweep not committed; no producer in the repo"
5
+ },
6
+ "results": [
7
+ {
8
+ "name": "ref: full 768 ridge",
9
+ "params": 769,
10
+ "F1": 0.9598035216331482,
11
+ "precision": 0.9898664355278015,
12
+ "recall": 0.9315127730369568,
13
+ "threshold": null
14
+ },
15
+ {
16
+ "name": "ref: K=92 ridge (cojoint top-92 + bias)",
17
+ "params": 93,
18
+ "F1": 0.9463722109794617,
19
+ "precision": 0.9854528307914734,
20
+ "recall": 0.9102730751037598,
21
+ "threshold": null
22
+ },
23
+ {
24
+ "name": "E: ternary weights top-50 pos vs top-50 neg, threshold",
25
+ "params": 1,
26
+ "F1": 0.8933987617492676,
27
+ "precision": 0.9200735092163086,
28
+ "recall": 0.8682271242141724,
29
+ "threshold": 31.819643020629883
30
+ },
31
+ {
32
+ "name": "C: threshold(sum top-20 pos \u2212 sum top-20 neg)",
33
+ "params": 1,
34
+ "F1": 0.8808632493019104,
35
+ "precision": 0.8952551484107971,
36
+ "recall": 0.8669267296791077,
37
+ "threshold": 24.8664608001709
38
+ },
39
+ {
40
+ "name": "E: ternary weights top-20 pos vs top-20 neg, threshold",
41
+ "params": 1,
42
+ "F1": 0.8808632493019104,
43
+ "precision": 0.8952551484107971,
44
+ "recall": 0.8669267296791077,
45
+ "threshold": 24.86646270751953
46
+ },
47
+ {
48
+ "name": "C: threshold(sum top-10 pos \u2212 sum top-10 neg)",
49
+ "params": 1,
50
+ "F1": 0.8801606893539429,
51
+ "precision": 0.9070836901664734,
52
+ "recall": 0.8547897934913635,
53
+ "threshold": 22.383634567260742
54
+ },
55
+ {
56
+ "name": "E: ternary weights top-10 pos vs top-10 neg, threshold",
57
+ "params": 1,
58
+ "F1": 0.8801606893539429,
59
+ "precision": 0.9070836901664734,
60
+ "recall": 0.8547897934913635,
61
+ "threshold": 22.383630752563477
62
+ },
63
+ {
64
+ "name": "B: threshold(sum top-2 pos dims)",
65
+ "params": 1,
66
+ "F1": 0.8780821561813354,
67
+ "precision": 0.9276410937309265,
68
+ "recall": 0.8335500359535217,
69
+ "threshold": 14.303534507751465
70
+ },
71
+ {
72
+ "name": "B: threshold(sum top-20 pos dims)",
73
+ "params": 1,
74
+ "F1": 0.8683924674987793,
75
+ "precision": 0.8716157078742981,
76
+ "recall": 0.8651928901672363,
77
+ "threshold": 62.11921310424805
78
+ },
79
+ {
80
+ "name": "C: threshold(sum top-5 pos \u2212 sum top-5 neg)",
81
+ "params": 1,
82
+ "F1": 0.8637353181838989,
83
+ "precision": 0.867512047290802,
84
+ "recall": 0.8599913120269775,
85
+ "threshold": 27.896242141723633
86
+ },
87
+ {
88
+ "name": "B: threshold(sum top-10 pos dims)",
89
+ "params": 1,
90
+ "F1": 0.8574432134628296,
91
+ "precision": 0.8325847387313843,
92
+ "recall": 0.883831799030304,
93
+ "threshold": 45.166908264160156
94
+ },
95
+ {
96
+ "name": "A: threshold(dim48)",
97
+ "params": 1,
98
+ "F1": 0.8451337814331055,
99
+ "precision": 0.8776844143867493,
100
+ "recall": 0.8149111270904541,
101
+ "threshold": 6.3439040184021
102
+ },
103
+ {
104
+ "name": "ref: K=1 ridge (dim48 + bias)",
105
+ "params": 2,
106
+ "F1": 0.8285356163978577,
107
+ "precision": 0.7985524535179138,
108
+ "recall": 0.8608582615852356,
109
+ "threshold": null
110
+ },
111
+ {
112
+ "name": "B: threshold(sum top-5 pos dims)",
113
+ "params": 1,
114
+ "F1": 0.8214052319526672,
115
+ "precision": 0.824454128742218,
116
+ "recall": 0.8183788657188416,
117
+ "threshold": 37.6262092590332
118
+ },
119
+ {
120
+ "name": "C: threshold(sum top-3 pos \u2212 sum top-3 neg)",
121
+ "params": 1,
122
+ "F1": 0.8199912905693054,
123
+ "precision": 0.8185744881629944,
124
+ "recall": 0.8214130997657776,
125
+ "threshold": 18.052114486694336
126
+ },
127
+ {
128
+ "name": "B: threshold(sum top-3 pos dims)",
129
+ "params": 1,
130
+ "F1": 0.7914334535598755,
131
+ "precision": 0.7131432294845581,
132
+ "recall": 0.8890333771705627,
133
+ "threshold": 23.4901123046875
134
+ },
135
+ {
136
+ "name": "D: threshold(max top-3 pos \u2212 max top-3 neg)",
137
+ "params": 1,
138
+ "F1": 0.7337717413902283,
139
+ "precision": 0.6004415154457092,
140
+ "recall": 0.9432163238525391,
141
+ "threshold": 4.771557331085205
142
+ },
143
+ {
144
+ "name": "D: threshold(max top-5 pos \u2212 max top-5 neg)",
145
+ "params": 1,
146
+ "F1": 0.7142618894577026,
147
+ "precision": 0.5805314779281616,
148
+ "recall": 0.9280450940132141,
149
+ "threshold": 6.046311378479004
150
+ },
151
+ {
152
+ "name": "D: threshold(max top-10 pos \u2212 max top-10 neg)",
153
+ "params": 1,
154
+ "F1": 0.7102322578430176,
155
+ "precision": 0.5886545181274414,
156
+ "recall": 0.8951018452644348,
157
+ "threshold": 4.326292991638184
158
+ },
159
+ {
160
+ "name": "D: threshold(max top-20 pos \u2212 max top-20 neg)",
161
+ "params": 1,
162
+ "F1": 0.7102322578430176,
163
+ "precision": 0.5886545181274414,
164
+ "recall": 0.8951018452644348,
165
+ "threshold": 4.326292991638184
166
+ }
167
+ ],
168
+ "top_pos_dims_30": [
169
+ 48,
170
+ 525,
171
+ 475,
172
+ 645,
173
+ 273,
174
+ 292,
175
+ 158,
176
+ 510,
177
+ 506,
178
+ 337,
179
+ 8,
180
+ 309,
181
+ 267,
182
+ 217,
183
+ 79,
184
+ 13,
185
+ 657,
186
+ 207,
187
+ 722,
188
+ 311,
189
+ 566,
190
+ 278,
191
+ 25,
192
+ 627,
193
+ 511,
194
+ 332,
195
+ 654,
196
+ 719,
197
+ 593,
198
+ 305
199
+ ],
200
+ "top_neg_dims_30": [
201
+ 642,
202
+ 224,
203
+ 113,
204
+ 565,
205
+ 49,
206
+ 637,
207
+ 45,
208
+ 520,
209
+ 219,
210
+ 290,
211
+ 529,
212
+ 617,
213
+ 269,
214
+ 745,
215
+ 576,
216
+ 701,
217
+ 105,
218
+ 694,
219
+ 82,
220
+ 283,
221
+ 574,
222
+ 310,
223
+ 613,
224
+ 90,
225
+ 92,
226
+ 650,
227
+ 36,
228
+ 53,
229
+ 396,
230
+ 17
231
+ ]
232
+ }
eval.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "provenance": {
3
+ "generator": "verify.py",
4
+ "classifier": "classifier.json",
5
+ "classifier_sha256": "19b70ff5e4e2977b80bac7e8915acd70dfe42691ae7fa55470c7f1a2580d5d0c",
6
+ "pool": "VAL5000",
7
+ "split": "val2017",
8
+ "n_images": 5000,
9
+ "positive_rate": 0.539,
10
+ "task": "image-level person presence (binary)",
11
+ "protocol": "live backbone forward at 768 px, no feature caching",
12
+ "selection": "the first 5000 val2017 image ids in sorted order, which is the whole split"
13
+ },
14
+ "metrics": {
15
+ "F1": 0.8886,
16
+ "precision": 0.9011,
17
+ "recall": 0.8763,
18
+ "threshold": 25.284494400024414
19
+ }
20
+ }
eval_tight_fpr.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "provenance": {
3
+ "generator": null,
4
+ "classifier": "classifier_tight_fpr.json",
5
+ "classifier_sha256": "21da0c971c21e3504de458f97c396df23c8cbc97ad3813435703c8e9b89f66ef",
6
+ "pool": null,
7
+ "task": "image-level person presence (binary)",
8
+ "measured_by": "stage_0/discovery/prop_specificity.json, extra_neg_k=15"
9
+ },
10
+ "metrics": {
11
+ "F1": 0.8527,
12
+ "precision": 0.8967,
13
+ "recall": 0.8127,
14
+ "threshold": 10.094175338745117
15
+ },
16
+ "prop_false_positive_rate": 0.0272,
17
+ "baseline": {
18
+ "F1": 0.8847,
19
+ "prop_false_positive_rate": 0.0593
20
+ }
21
+ }
head.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """The classifier as a fused Linear with fixed ternary weights and one free bias.
2
+
3
+ model = FusedClassifier.from_hub()
4
+ score, present = model(image_tensor)
5
+
6
+ Constructed without a backbone the module still exposes `head`, the same
7
+ decision applied to an already-pooled vector.
8
+ """
9
+ import argparse
10
+ import json
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ import torch
15
+ import torch.nn as nn
16
+ import torch.nn.functional as F
17
+
18
+ sys.path.insert(0, str(Path(__file__).resolve().parent)) # repo root, for `common`
19
+ from common import BACKBONE, pool # noqa: E402
20
+
21
+ HERE = Path(__file__).resolve().parent
22
+
23
+
24
+ class FusedClassifier(nn.Module):
25
+ """Backbone -> 40-dim slice -> ternary linear head -> binary decision.
26
+
27
+ `retained_dims` indexes the 768-D pooled vector; `retained_weight` is +1 on
28
+ the person-positive positions and -1 on the person-negative ones. The
29
+ threshold is the only free parameter.
30
+ """
31
+
32
+ def __init__(self, backbone, pos_dims, neg_dims, threshold):
33
+ super().__init__()
34
+ self.backbone = backbone
35
+ retained = list(pos_dims) + list(neg_dims)
36
+ self.register_buffer('retained_dims', torch.tensor(retained, dtype=torch.long))
37
+ w = torch.zeros(1, len(retained))
38
+ w[0, :len(pos_dims)] = 1.0
39
+ w[0, len(pos_dims):] = -1.0
40
+ self.register_buffer('retained_weight', w)
41
+ self.threshold = nn.Parameter(torch.tensor(float(threshold)))
42
+
43
+ def head(self, pooled):
44
+ """(..., 768) pooled vector -> (score, present), no backbone involved."""
45
+ retained = pooled.index_select(-1, self.retained_dims)
46
+ score = F.linear(retained, self.retained_weight).squeeze(-1)
47
+ return score, score > self.threshold
48
+
49
+ @torch.inference_mode()
50
+ def forward(self, x):
51
+ """x: (B, 3, 768, 768) normalized. Returns (score (B,), present (B,))."""
52
+ if self.backbone is None:
53
+ raise RuntimeError('constructed without a backbone; use .head(pooled)')
54
+ dev = 'cuda' if x.is_cuda else 'cpu'
55
+ with torch.autocast(dev, dtype=torch.bfloat16):
56
+ out = self.backbone.forward_features(x)
57
+ return self.head(pool(out['x_norm_patchtokens'].float()))
58
+
59
+ @classmethod
60
+ def from_config(cls, backbone=None, classifier_json=None):
61
+ c = json.loads(Path(classifier_json or HERE / 'classifier.json').read_text())
62
+ return cls(backbone, c['pos_dims'], c['neg_dims'], c['threshold'])
63
+
64
+ @classmethod
65
+ def from_hub(cls, repo_or_path=None, classifier_json=None):
66
+ from common.models import load_backbone
67
+ return cls.from_config(load_backbone(repo_or_path or BACKBONE), classifier_json)
68
+
69
+
70
+ if __name__ == '__main__':
71
+ ap = argparse.ArgumentParser(description=__doc__)
72
+ ap.add_argument('--classifier', type=Path, default=HERE / 'classifier.json')
73
+ args = ap.parse_args()
74
+ m = FusedClassifier.from_hub(classifier_json=args.classifier).eval()
75
+ n_all = sum(p.numel() for p in m.parameters())
76
+ n_backbone = sum(p.numel() for p in m.backbone.parameters())
77
+ print(f'total params: {n_all:,}')
78
+ print(f'backbone params: {n_backbone:,}')
79
+ print(f'head params: {n_all - n_backbone} '
80
+ f'(one learnable threshold; weights are fixed buffers)')
81
+ print(f'retained dims: {m.retained_dims.numel()}')
infer.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Load a classifier variant and score images for person presence.
2
+
3
+ from infer import PersonDetector
4
+ det = PersonDetector.load('baseline')
5
+ score, present = det.predict('image.jpg')
6
+
7
+ `score` is positive for person scenes; `present` is `score > threshold`.
8
+
9
+ Variants
10
+ baseline the classifier
11
+ tight_fpr the same, with 15 extra prop-suppressing negative dims
12
+ """
13
+ import argparse
14
+ import json
15
+ import sys
16
+ from pathlib import Path
17
+ from typing import Tuple
18
+
19
+ import torch
20
+
21
+ sys.path.insert(0, str(Path(__file__).resolve().parent)) # repo root, for `common`
22
+ from common import BACKBONE, RES, backbone_pooled, device, load_image, score # noqa: E402
23
+ from common.models import load_backbone # noqa: E402
24
+
25
+ HERE = Path(__file__).resolve().parent
26
+ CONFIGS = {'baseline': 'classifier.json', 'tight_fpr': 'classifier_tight_fpr.json'}
27
+
28
+
29
+ class PersonDetector:
30
+ def __init__(self, forward_fn, pos_dims, neg_dims, threshold, dev):
31
+ self._forward = forward_fn
32
+ self._dev = dev
33
+ self._pos = torch.tensor(pos_dims, dtype=torch.long, device=dev)
34
+ self._neg = torch.tensor(neg_dims, dtype=torch.long, device=dev)
35
+ self._thr = float(threshold)
36
+
37
+ @property
38
+ def threshold(self) -> float:
39
+ return self._thr
40
+
41
+ @torch.inference_mode()
42
+ def predict(self, image) -> Tuple[float, bool]:
43
+ pooled = self._forward(load_image(image, RES, self._dev))
44
+ s = float(score(pooled, self._pos, self._neg))
45
+ return s, s > self._thr
46
+
47
+ @classmethod
48
+ def load(cls, variant: str = 'baseline', backbone_repo: str = BACKBONE,
49
+ root=None) -> 'PersonDetector':
50
+ root = Path(root) if root else HERE
51
+ if variant not in CONFIGS:
52
+ raise ValueError(f'unknown variant {variant!r}; expected one of '
53
+ f'{sorted(CONFIGS)}')
54
+ dev = device()
55
+ backbone = load_backbone(backbone_repo).to(dev)
56
+ c = cls._classifier(root / CONFIGS[variant])
57
+ return cls(lambda x: backbone_pooled(backbone, x)[0],
58
+ c['pos_dims'], c['neg_dims'], c['threshold'], dev)
59
+
60
+ @staticmethod
61
+ def _classifier(path) -> dict:
62
+ """Read a classifier config, cross-checking the safetensors beside it."""
63
+ path = Path(path)
64
+ c = json.loads(path.read_text())
65
+ weights = path.with_suffix('.safetensors')
66
+ if weights.exists():
67
+ from safetensors.torch import load_file
68
+ t = load_file(str(weights))
69
+ for key in ('pos_dims', 'neg_dims'):
70
+ if t[key].tolist() != c[key]:
71
+ raise ValueError(f'{weights.name} disagrees with {path.name} on {key}')
72
+ if abs(float(t['threshold'][0]) - float(c['threshold'])) > 1e-4:
73
+ raise ValueError(f'{weights.name} disagrees with {path.name} on threshold')
74
+ return c
75
+
76
+
77
+ if __name__ == '__main__':
78
+ ap = argparse.ArgumentParser(description=__doc__)
79
+ ap.add_argument('variant', choices=sorted(CONFIGS))
80
+ ap.add_argument('images', nargs='+')
81
+ args = ap.parse_args()
82
+ det = PersonDetector.load(args.variant)
83
+ for path in args.images:
84
+ s, present = det.predict(path)
85
+ print(f'{path} score={s:+.3f} threshold={det.threshold:+.3f} person={present}')
per_dim_thresholds.json ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "provenance": {
3
+ "generator": "calibrate.py",
4
+ "classifier": "classifier.json",
5
+ "classifier_sha256": "19b70ff5e4e2977b80bac7e8915acd70dfe42691ae7fa55470c7f1a2580d5d0c",
6
+ "pool": "BALANCED_VAL",
7
+ "split": "val2017",
8
+ "n_images": 4614,
9
+ "positive_rate": 0.5,
10
+ "seed": 0,
11
+ "selection": "val2017 subsampled without replacement to equal person-positive and person-negative counts"
12
+ },
13
+ "quant_scale": 8,
14
+ "per_dim_thresholds": [
15
+ {
16
+ "dim_index_in_40": 0,
17
+ "dim_global": 48,
18
+ "is_pos": true,
19
+ "threshold": 6.572150230407715,
20
+ "threshold_int8": 53,
21
+ "per_dim_F1": 0.8464522361755371
22
+ },
23
+ {
24
+ "dim_index_in_40": 1,
25
+ "dim_global": 525,
26
+ "is_pos": true,
27
+ "threshold": 7.047104358673096,
28
+ "threshold_int8": 56,
29
+ "per_dim_F1": 0.7466137409210205
30
+ },
31
+ {
32
+ "dim_index_in_40": 2,
33
+ "dim_global": 475,
34
+ "is_pos": true,
35
+ "threshold": 3.269585132598877,
36
+ "threshold_int8": 26,
37
+ "per_dim_F1": 0.6843164563179016
38
+ },
39
+ {
40
+ "dim_index_in_40": 3,
41
+ "dim_global": 645,
42
+ "is_pos": true,
43
+ "threshold": 7.4769134521484375,
44
+ "threshold_int8": 60,
45
+ "per_dim_F1": 0.7112371325492859
46
+ },
47
+ {
48
+ "dim_index_in_40": 4,
49
+ "dim_global": 273,
50
+ "is_pos": true,
51
+ "threshold": 2.0735650062561035,
52
+ "threshold_int8": 17,
53
+ "per_dim_F1": 0.7335397005081177
54
+ },
55
+ {
56
+ "dim_index_in_40": 5,
57
+ "dim_global": 292,
58
+ "is_pos": true,
59
+ "threshold": 1.5521039962768555,
60
+ "threshold_int8": 12,
61
+ "per_dim_F1": 0.732986569404602
62
+ },
63
+ {
64
+ "dim_index_in_40": 6,
65
+ "dim_global": 158,
66
+ "is_pos": true,
67
+ "threshold": 2.054447889328003,
68
+ "threshold_int8": 16,
69
+ "per_dim_F1": 0.6832557320594788
70
+ },
71
+ {
72
+ "dim_index_in_40": 7,
73
+ "dim_global": 510,
74
+ "is_pos": true,
75
+ "threshold": 0.10592363774776459,
76
+ "threshold_int8": 1,
77
+ "per_dim_F1": 0.6806007027626038
78
+ },
79
+ {
80
+ "dim_index_in_40": 8,
81
+ "dim_global": 506,
82
+ "is_pos": true,
83
+ "threshold": 0.642810583114624,
84
+ "threshold_int8": 5,
85
+ "per_dim_F1": 0.7115705609321594
86
+ },
87
+ {
88
+ "dim_index_in_40": 9,
89
+ "dim_global": 337,
90
+ "is_pos": true,
91
+ "threshold": 1.3417854309082031,
92
+ "threshold_int8": 11,
93
+ "per_dim_F1": 0.705616295337677
94
+ },
95
+ {
96
+ "dim_index_in_40": 10,
97
+ "dim_global": 8,
98
+ "is_pos": true,
99
+ "threshold": 0.520650327205658,
100
+ "threshold_int8": 4,
101
+ "per_dim_F1": 0.6678624749183655
102
+ },
103
+ {
104
+ "dim_index_in_40": 11,
105
+ "dim_global": 309,
106
+ "is_pos": true,
107
+ "threshold": 0.44822290539741516,
108
+ "threshold_int8": 4,
109
+ "per_dim_F1": 0.6864839196205139
110
+ },
111
+ {
112
+ "dim_index_in_40": 12,
113
+ "dim_global": 267,
114
+ "is_pos": true,
115
+ "threshold": 0.7230344414710999,
116
+ "threshold_int8": 6,
117
+ "per_dim_F1": 0.7057974338531494
118
+ },
119
+ {
120
+ "dim_index_in_40": 13,
121
+ "dim_global": 217,
122
+ "is_pos": true,
123
+ "threshold": 1.1072767972946167,
124
+ "threshold_int8": 9,
125
+ "per_dim_F1": 0.6852783560752869
126
+ },
127
+ {
128
+ "dim_index_in_40": 14,
129
+ "dim_global": 79,
130
+ "is_pos": true,
131
+ "threshold": 0.9621400237083435,
132
+ "threshold_int8": 8,
133
+ "per_dim_F1": 0.6922308206558228
134
+ },
135
+ {
136
+ "dim_index_in_40": 15,
137
+ "dim_global": 13,
138
+ "is_pos": true,
139
+ "threshold": 2.015596389770508,
140
+ "threshold_int8": 16,
141
+ "per_dim_F1": 0.6755585670471191
142
+ },
143
+ {
144
+ "dim_index_in_40": 16,
145
+ "dim_global": 657,
146
+ "is_pos": true,
147
+ "threshold": 0.7083938121795654,
148
+ "threshold_int8": 6,
149
+ "per_dim_F1": 0.6905635595321655
150
+ },
151
+ {
152
+ "dim_index_in_40": 17,
153
+ "dim_global": 207,
154
+ "is_pos": true,
155
+ "threshold": 0.7881279587745667,
156
+ "threshold_int8": 6,
157
+ "per_dim_F1": 0.7052351236343384
158
+ },
159
+ {
160
+ "dim_index_in_40": 18,
161
+ "dim_global": 722,
162
+ "is_pos": true,
163
+ "threshold": 0.895519495010376,
164
+ "threshold_int8": 7,
165
+ "per_dim_F1": 0.6906405091285706
166
+ },
167
+ {
168
+ "dim_index_in_40": 19,
169
+ "dim_global": 311,
170
+ "is_pos": true,
171
+ "threshold": 1.051217794418335,
172
+ "threshold_int8": 8,
173
+ "per_dim_F1": 0.6888962388038635
174
+ },
175
+ {
176
+ "dim_index_in_40": 20,
177
+ "dim_global": 642,
178
+ "is_pos": false,
179
+ "threshold": 4.502034664154053,
180
+ "threshold_int8": 36,
181
+ "per_dim_F1": 0.6846261024475098
182
+ },
183
+ {
184
+ "dim_index_in_40": 21,
185
+ "dim_global": 224,
186
+ "is_pos": false,
187
+ "threshold": 5.5922465324401855,
188
+ "threshold_int8": 45,
189
+ "per_dim_F1": 0.6726456880569458
190
+ },
191
+ {
192
+ "dim_index_in_40": 22,
193
+ "dim_global": 113,
194
+ "is_pos": false,
195
+ "threshold": 2.338114023208618,
196
+ "threshold_int8": 19,
197
+ "per_dim_F1": 0.6799814701080322
198
+ },
199
+ {
200
+ "dim_index_in_40": 23,
201
+ "dim_global": 565,
202
+ "is_pos": false,
203
+ "threshold": 1.811521291732788,
204
+ "threshold_int8": 14,
205
+ "per_dim_F1": 0.6747174263000488
206
+ },
207
+ {
208
+ "dim_index_in_40": 24,
209
+ "dim_global": 49,
210
+ "is_pos": false,
211
+ "threshold": 1.7180224657058716,
212
+ "threshold_int8": 14,
213
+ "per_dim_F1": 0.680044949054718
214
+ },
215
+ {
216
+ "dim_index_in_40": 25,
217
+ "dim_global": 637,
218
+ "is_pos": false,
219
+ "threshold": 7.861576080322266,
220
+ "threshold_int8": 63,
221
+ "per_dim_F1": 0.6734788417816162
222
+ },
223
+ {
224
+ "dim_index_in_40": 26,
225
+ "dim_global": 45,
226
+ "is_pos": false,
227
+ "threshold": 2.251237630844116,
228
+ "threshold_int8": 18,
229
+ "per_dim_F1": 0.6706920266151428
230
+ },
231
+ {
232
+ "dim_index_in_40": 27,
233
+ "dim_global": 520,
234
+ "is_pos": false,
235
+ "threshold": 6.079483985900879,
236
+ "threshold_int8": 49,
237
+ "per_dim_F1": 0.675870954990387
238
+ },
239
+ {
240
+ "dim_index_in_40": 28,
241
+ "dim_global": 219,
242
+ "is_pos": false,
243
+ "threshold": 2.141653060913086,
244
+ "threshold_int8": 17,
245
+ "per_dim_F1": 0.668889582157135
246
+ },
247
+ {
248
+ "dim_index_in_40": 29,
249
+ "dim_global": 290,
250
+ "is_pos": false,
251
+ "threshold": 2.1265642642974854,
252
+ "threshold_int8": 17,
253
+ "per_dim_F1": 0.6621823906898499
254
+ },
255
+ {
256
+ "dim_index_in_40": 30,
257
+ "dim_global": 529,
258
+ "is_pos": false,
259
+ "threshold": 2.183765411376953,
260
+ "threshold_int8": 17,
261
+ "per_dim_F1": 0.669143795967102
262
+ },
263
+ {
264
+ "dim_index_in_40": 31,
265
+ "dim_global": 617,
266
+ "is_pos": false,
267
+ "threshold": 2.6399528980255127,
268
+ "threshold_int8": 21,
269
+ "per_dim_F1": 0.6687593460083008
270
+ },
271
+ {
272
+ "dim_index_in_40": 32,
273
+ "dim_global": 269,
274
+ "is_pos": false,
275
+ "threshold": 1.357992172241211,
276
+ "threshold_int8": 11,
277
+ "per_dim_F1": 0.6660473942756653
278
+ },
279
+ {
280
+ "dim_index_in_40": 33,
281
+ "dim_global": 745,
282
+ "is_pos": false,
283
+ "threshold": 2.195744514465332,
284
+ "threshold_int8": 18,
285
+ "per_dim_F1": 0.6753367185592651
286
+ },
287
+ {
288
+ "dim_index_in_40": 34,
289
+ "dim_global": 576,
290
+ "is_pos": false,
291
+ "threshold": 2.358708620071411,
292
+ "threshold_int8": 19,
293
+ "per_dim_F1": 0.6699551939964294
294
+ },
295
+ {
296
+ "dim_index_in_40": 35,
297
+ "dim_global": 701,
298
+ "is_pos": false,
299
+ "threshold": 1.7730076313018799,
300
+ "threshold_int8": 14,
301
+ "per_dim_F1": 0.670412540435791
302
+ },
303
+ {
304
+ "dim_index_in_40": 36,
305
+ "dim_global": 105,
306
+ "is_pos": false,
307
+ "threshold": 2.2044856548309326,
308
+ "threshold_int8": 18,
309
+ "per_dim_F1": 0.6687593460083008
310
+ },
311
+ {
312
+ "dim_index_in_40": 37,
313
+ "dim_global": 694,
314
+ "is_pos": false,
315
+ "threshold": 2.0147933959960938,
316
+ "threshold_int8": 16,
317
+ "per_dim_F1": 0.6694493293762207
318
+ },
319
+ {
320
+ "dim_index_in_40": 38,
321
+ "dim_global": 82,
322
+ "is_pos": false,
323
+ "threshold": 2.640681743621826,
324
+ "threshold_int8": 21,
325
+ "per_dim_F1": 0.6809103488922119
326
+ },
327
+ {
328
+ "dim_index_in_40": 39,
329
+ "dim_global": 283,
330
+ "is_pos": false,
331
+ "threshold": 1.7581170797348022,
332
+ "threshold_int8": 14,
333
+ "per_dim_F1": 0.6612855195999146
334
+ }
335
+ ],
336
+ "popcount": {
337
+ "final_threshold": 13,
338
+ "F1": 0.8764044642448425,
339
+ "precision": 0.8911290168762207,
340
+ "recall": 0.8621586561203003
341
+ },
342
+ "additive": {
343
+ "F1": 0.8842884302139282,
344
+ "precision": 0.8891323208808899,
345
+ "recall": 0.8794971704483032
346
+ },
347
+ "F1_delta_popcount_vs_additive": -0.007883965969085693
348
+ }
pyproject.toml ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "one-parameter-classifier"
7
+ version = "0.0.0"
8
+ description = "Image-level person classification on EUPE-ViT-B features with a single free parameter"
9
+ requires-python = ">=3.9"
10
+ dependencies = [
11
+ "torch>=2.0",
12
+ "numpy",
13
+ "pillow",
14
+ "safetensors",
15
+ "transformers>=4.40",
16
+ "huggingface-hub",
17
+ "pycocotools",
18
+ ]
19
+
20
+ [project.optional-dependencies]
21
+ dev = ["pytest>=7"]
22
+ synth = ["nosis"]
23
+
24
+ [tool.setuptools]
25
+ packages = ["common"]
26
+ py-modules = ["infer", "head", "verify", "calibrate", "rtl_gen", "synth"]
27
+
28
+ [tool.pytest.ini_options]
29
+ testpaths = ["tests"]
rtl/popcount.v ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Popcount-reformulated 1-parameter person classifier, runtime thresholds.
2
+ // Generated by rtl_gen.py; do not edit by hand.
3
+ //
4
+ // Inputs are the 40 Stage 0 classifier dims as signed INT8, post-LayerNorm and
5
+ // post-max-pool. Output is one bit. Combinational, no multipliers, no memory.
6
+
7
+ module person_classifier_popcount (
8
+ input signed [7:0] f00, f01, f02, f03, f04, f05, f06, f07, f08, f09,
9
+ input signed [7:0] f10, f11, f12, f13, f14, f15, f16, f17, f18, f19,
10
+ input signed [7:0] f20, f21, f22, f23, f24, f25, f26, f27, f28, f29,
11
+ input signed [7:0] f30, f31, f32, f33, f34, f35, f36, f37, f38, f39,
12
+ input signed [7:0] t00, t01, t02, t03, t04, t05, t06, t07, t08, t09,
13
+ input signed [7:0] t10, t11, t12, t13, t14, t15, t16, t17, t18, t19,
14
+ input signed [7:0] t20, t21, t22, t23, t24, t25, t26, t27, t28, t29,
15
+ input signed [7:0] t30, t31, t32, t33, t34, t35, t36, t37, t38, t39,
16
+ input signed [5:0] final_threshold,
17
+ output person_present
18
+ );
19
+ wire [5:0] count_pos =
20
+ (f00 > t00) + (f01 > t01) + (f02 > t02) +
21
+ (f03 > t03) + (f04 > t04) + (f05 > t05) +
22
+ (f06 > t06) + (f07 > t07) + (f08 > t08) +
23
+ (f09 > t09) + (f10 > t10) + (f11 > t11) +
24
+ (f12 > t12) + (f13 > t13) + (f14 > t14) +
25
+ (f15 > t15) + (f16 > t16) + (f17 > t17) +
26
+ (f18 > t18) + (f19 > t19);
27
+
28
+ wire [5:0] count_neg =
29
+ (f20 > t20) + (f21 > t21) + (f22 > t22) +
30
+ (f23 > t23) + (f24 > t24) + (f25 > t25) +
31
+ (f26 > t26) + (f27 > t27) + (f28 > t28) +
32
+ (f29 > t29) + (f30 > t30) + (f31 > t31) +
33
+ (f32 > t32) + (f33 > t33) + (f34 > t34) +
34
+ (f35 > t35) + (f36 > t36) + (f37 > t37) +
35
+ (f38 > t38) + (f39 > t39);
36
+
37
+ wire signed [6:0] diff = {1'b0, count_pos} - {1'b0, count_neg};
38
+ assign person_present = diff > final_threshold;
39
+ endmodule
rtl/popcount_folded.v ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Popcount-reformulated 1-parameter person classifier, thresholds baked in.
2
+ // Generated by rtl_gen.py; do not edit by hand.
3
+ //
4
+ // Inputs are the 40 Stage 0 classifier dims as signed INT8, post-LayerNorm and
5
+ // post-max-pool. Output is one bit. Combinational, no multipliers, no memory.
6
+ //
7
+ // Per-dim thresholds are the calibrated float values scaled by 8 and rounded.
8
+
9
+ module person_classifier_popcount_folded (
10
+ input signed [7:0] f00, f01, f02, f03, f04, f05, f06, f07, f08, f09,
11
+ input signed [7:0] f10, f11, f12, f13, f14, f15, f16, f17, f18, f19,
12
+ input signed [7:0] f20, f21, f22, f23, f24, f25, f26, f27, f28, f29,
13
+ input signed [7:0] f30, f31, f32, f33, f34, f35, f36, f37, f38, f39,
14
+ output person_present
15
+ );
16
+ localparam signed [7:0] T00 = 53, T01 = 56, T02 = 26, T03 = 60, T04 = 17,
17
+ T05 = 12, T06 = 16, T07 = 1, T08 = 5, T09 = 11,
18
+ T10 = 4, T11 = 4, T12 = 6, T13 = 9, T14 = 8,
19
+ T15 = 16, T16 = 6, T17 = 6, T18 = 7, T19 = 8,
20
+ T20 = 36, T21 = 45, T22 = 19, T23 = 14, T24 = 14,
21
+ T25 = 63, T26 = 18, T27 = 49, T28 = 17, T29 = 17,
22
+ T30 = 17, T31 = 21, T32 = 11, T33 = 18, T34 = 19,
23
+ T35 = 14, T36 = 18, T37 = 16, T38 = 21, T39 = 14;
24
+ localparam signed [5:0] FINAL_T = 13;
25
+
26
+ wire [5:0] count_pos =
27
+ (f00 > T00) + (f01 > T01) + (f02 > T02) +
28
+ (f03 > T03) + (f04 > T04) + (f05 > T05) +
29
+ (f06 > T06) + (f07 > T07) + (f08 > T08) +
30
+ (f09 > T09) + (f10 > T10) + (f11 > T11) +
31
+ (f12 > T12) + (f13 > T13) + (f14 > T14) +
32
+ (f15 > T15) + (f16 > T16) + (f17 > T17) +
33
+ (f18 > T18) + (f19 > T19);
34
+
35
+ wire [5:0] count_neg =
36
+ (f20 > T20) + (f21 > T21) + (f22 > T22) +
37
+ (f23 > T23) + (f24 > T24) + (f25 > T25) +
38
+ (f26 > T26) + (f27 > T27) + (f28 > T28) +
39
+ (f29 > T29) + (f30 > T30) + (f31 > T31) +
40
+ (f32 > T32) + (f33 > T33) + (f34 > T34) +
41
+ (f35 > T35) + (f36 > T36) + (f37 > T37) +
42
+ (f38 > T38) + (f39 > T39);
43
+
44
+ wire signed [6:0] diff = {1'b0, count_pos} - {1'b0, count_neg};
45
+ assign person_present = diff > FINAL_T;
46
+ endmodule
rtl/sum.v ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Additive 1-parameter person classifier, runtime threshold.
2
+ // Generated by rtl_gen.py; do not edit by hand.
3
+ //
4
+ // Inputs are the 40 Stage 0 classifier dims as signed INT8, post-LayerNorm and
5
+ // post-max-pool. Output is one bit. Combinational, no multipliers, no memory.
6
+
7
+ module person_classifier_1p (
8
+ input signed [7:0] f00, f01, f02, f03, f04, f05, f06, f07, f08, f09,
9
+ input signed [7:0] f10, f11, f12, f13, f14, f15, f16, f17, f18, f19,
10
+ input signed [7:0] f20, f21, f22, f23, f24, f25, f26, f27, f28, f29,
11
+ input signed [7:0] f30, f31, f32, f33, f34, f35, f36, f37, f38, f39,
12
+ input signed [15:0] threshold,
13
+ output person_present
14
+ );
15
+ // Score = sum(f00..f19) - sum(f20..f39); worst case 20 * 127 = 2540 fits in 16 bits.
16
+ wire signed [15:0] pos_sum =
17
+ {{8{f00[7]}}, f00} + {{8{f01[7]}}, f01} + {{8{f02[7]}}, f02} + {{8{f03[7]}}, f03} +
18
+ {{8{f04[7]}}, f04} + {{8{f05[7]}}, f05} + {{8{f06[7]}}, f06} + {{8{f07[7]}}, f07} +
19
+ {{8{f08[7]}}, f08} + {{8{f09[7]}}, f09} + {{8{f10[7]}}, f10} + {{8{f11[7]}}, f11} +
20
+ {{8{f12[7]}}, f12} + {{8{f13[7]}}, f13} + {{8{f14[7]}}, f14} + {{8{f15[7]}}, f15} +
21
+ {{8{f16[7]}}, f16} + {{8{f17[7]}}, f17} + {{8{f18[7]}}, f18} + {{8{f19[7]}}, f19};
22
+
23
+ wire signed [15:0] neg_sum =
24
+ {{8{f20[7]}}, f20} + {{8{f21[7]}}, f21} + {{8{f22[7]}}, f22} + {{8{f23[7]}}, f23} +
25
+ {{8{f24[7]}}, f24} + {{8{f25[7]}}, f25} + {{8{f26[7]}}, f26} + {{8{f27[7]}}, f27} +
26
+ {{8{f28[7]}}, f28} + {{8{f29[7]}}, f29} + {{8{f30[7]}}, f30} + {{8{f31[7]}}, f31} +
27
+ {{8{f32[7]}}, f32} + {{8{f33[7]}}, f33} + {{8{f34[7]}}, f34} + {{8{f35[7]}}, f35} +
28
+ {{8{f36[7]}}, f36} + {{8{f37[7]}}, f37} + {{8{f38[7]}}, f38} + {{8{f39[7]}}, f39};
29
+
30
+ wire signed [15:0] score = pos_sum - neg_sum;
31
+ assign person_present = score > threshold;
32
+ endmodule
rtl/sum_folded.v ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Additive 1-parameter person classifier, threshold baked in.
2
+ // Generated by rtl_gen.py; do not edit by hand.
3
+ //
4
+ // Inputs are the 40 Stage 0 classifier dims as signed INT8, post-LayerNorm and
5
+ // post-max-pool. Output is one bit. Combinational, no multipliers, no memory.
6
+
7
+ module person_classifier_sum_folded (
8
+ input signed [7:0] f00, f01, f02, f03, f04, f05, f06, f07, f08, f09,
9
+ input signed [7:0] f10, f11, f12, f13, f14, f15, f16, f17, f18, f19,
10
+ input signed [7:0] f20, f21, f22, f23, f24, f25, f26, f27, f28, f29,
11
+ input signed [7:0] f30, f31, f32, f33, f34, f35, f36, f37, f38, f39,
12
+ output person_present
13
+ );
14
+ // Stage 0 threshold 25.2845 at the x8 scale used for the per-dim ones.
15
+ localparam signed [15:0] FINAL_T = 16'sd202;
16
+
17
+ wire signed [15:0] pos_sum =
18
+ {{8{f00[7]}}, f00} + {{8{f01[7]}}, f01} + {{8{f02[7]}}, f02} + {{8{f03[7]}}, f03} +
19
+ {{8{f04[7]}}, f04} + {{8{f05[7]}}, f05} + {{8{f06[7]}}, f06} + {{8{f07[7]}}, f07} +
20
+ {{8{f08[7]}}, f08} + {{8{f09[7]}}, f09} + {{8{f10[7]}}, f10} + {{8{f11[7]}}, f11} +
21
+ {{8{f12[7]}}, f12} + {{8{f13[7]}}, f13} + {{8{f14[7]}}, f14} + {{8{f15[7]}}, f15} +
22
+ {{8{f16[7]}}, f16} + {{8{f17[7]}}, f17} + {{8{f18[7]}}, f18} + {{8{f19[7]}}, f19};
23
+
24
+ wire signed [15:0] neg_sum =
25
+ {{8{f20[7]}}, f20} + {{8{f21[7]}}, f21} + {{8{f22[7]}}, f22} + {{8{f23[7]}}, f23} +
26
+ {{8{f24[7]}}, f24} + {{8{f25[7]}}, f25} + {{8{f26[7]}}, f26} + {{8{f27[7]}}, f27} +
27
+ {{8{f28[7]}}, f28} + {{8{f29[7]}}, f29} + {{8{f30[7]}}, f30} + {{8{f31[7]}}, f31} +
28
+ {{8{f32[7]}}, f32} + {{8{f33[7]}}, f33} + {{8{f34[7]}}, f34} + {{8{f35[7]}}, f35} +
29
+ {{8{f36[7]}}, f36} + {{8{f37[7]}}, f37} + {{8{f38[7]}}, f38} + {{8{f39[7]}}, f39};
30
+
31
+ wire signed [15:0] score = pos_sum - neg_sum;
32
+ assign person_present = score > FINAL_T;
33
+ endmodule
rtl_gen.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generate all four RTL variants from per_dim_thresholds.json.
2
+
3
+ python rtl_gen.py
4
+
5
+ Kept separate from calibrate.py so the RTL can be regenerated from the committed
6
+ thresholds without the feature cache the calibration needs.
7
+ """
8
+ import argparse
9
+ import json
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ sys.path.insert(0, str(Path(__file__).resolve().parent)) # repo root, for `common`
14
+ from common import read_artifact # noqa: E402
15
+
16
+ HERE = Path(__file__).resolve().parent
17
+ N_DIMS = 40
18
+ N_POS = 20
19
+
20
+ HEADER = '''// {title}
21
+ // Generated by rtl_gen.py; do not edit by hand.
22
+ //
23
+ // Inputs are the 40 Stage 0 classifier dims as signed INT8, post-LayerNorm and
24
+ // post-max-pool. Output is one bit. Combinational, no multipliers, no memory.
25
+ '''
26
+
27
+
28
+ def _ports(name: str, width: int, per_line: int = 10) -> str:
29
+ """Declaration lines for f00..f39 or t00..t39."""
30
+ rows = []
31
+ for start in range(0, N_DIMS, per_line):
32
+ names = ', '.join(f'{name}{i:02d}' for i in range(start, start + per_line))
33
+ rows.append(f' input signed [{width - 1}:0] {names},')
34
+ return '\n'.join(rows)
35
+
36
+
37
+ def _sign_extended_sum(lo: int, hi: int, per_line: int = 4) -> str:
38
+ """Sign-extended 16-bit addition of f{lo}..f{hi-1}."""
39
+ terms = [f'{{{{8{{f{i:02d}[7]}}}}, f{i:02d}}}' for i in range(lo, hi)]
40
+ rows = [' + '.join(terms[i:i + per_line]) for i in range(0, len(terms), per_line)]
41
+ return ' +\n '.join(rows)
42
+
43
+
44
+ def _popcount(lo: int, hi: int, threshold, per_line: int = 3) -> str:
45
+ """Sum of the per-dim comparisons, written inline.
46
+
47
+ The comparisons are summed directly rather than collected into a vector and
48
+ indexed. Indexing a vector is the more readable form, but it relies on
49
+ bit-select lowering that the synthesis backend gets wrong at index 0, and a
50
+ decision circuit is not the place to depend on that.
51
+ """
52
+ terms = [f'(f{i:02d} > {threshold(i)})' for i in range(lo, hi)]
53
+ rows = [' + '.join(terms[i:i + per_line]) for i in range(0, len(terms), per_line)]
54
+ return ' +\n '.join(rows)
55
+
56
+
57
+ def _threshold_bank(values, per_line: int = 5) -> str:
58
+ """localparam bank holding the 40 baked INT8 thresholds."""
59
+ rows = []
60
+ for start in range(0, N_DIMS, per_line):
61
+ rows.append(', '.join(f'T{i:02d} = {values[i]:>4}'
62
+ for i in range(start, min(start + per_line, N_DIMS))))
63
+ return (',\n' + ' ' * 29).join(rows)
64
+
65
+
66
+ def emit_sum() -> str:
67
+ return HEADER.format(title='Additive 1-parameter person classifier, runtime threshold.') + f'''
68
+ module person_classifier_1p (
69
+ {_ports('f', 8)}
70
+ input signed [15:0] threshold,
71
+ output person_present
72
+ );
73
+ // Score = sum(f00..f19) - sum(f20..f39); worst case 20 * 127 = 2540 fits in 16 bits.
74
+ wire signed [15:0] pos_sum =
75
+ {_sign_extended_sum(0, 20)};
76
+
77
+ wire signed [15:0] neg_sum =
78
+ {_sign_extended_sum(20, 40)};
79
+
80
+ wire signed [15:0] score = pos_sum - neg_sum;
81
+ assign person_present = score > threshold;
82
+ endmodule
83
+ '''
84
+
85
+
86
+ def emit_sum_folded(final_int8: int, final_float: float, quant_scale: int) -> str:
87
+ return HEADER.format(title='Additive 1-parameter person classifier, threshold baked in.') + f'''
88
+ module person_classifier_sum_folded (
89
+ {_ports('f', 8)}
90
+ output person_present
91
+ );
92
+ // Stage 0 threshold {final_float:.4f} at the x{quant_scale} scale used for the per-dim ones.
93
+ localparam signed [15:0] FINAL_T = 16'sd{final_int8};
94
+
95
+ wire signed [15:0] pos_sum =
96
+ {_sign_extended_sum(0, 20)};
97
+
98
+ wire signed [15:0] neg_sum =
99
+ {_sign_extended_sum(20, 40)};
100
+
101
+ wire signed [15:0] score = pos_sum - neg_sum;
102
+ assign person_present = score > FINAL_T;
103
+ endmodule
104
+ '''
105
+
106
+
107
+ def emit_popcount() -> str:
108
+ title = 'Popcount-reformulated 1-parameter person classifier, runtime thresholds.'
109
+ return HEADER.format(title=title) + f'''
110
+ module person_classifier_popcount (
111
+ {_ports('f', 8)}
112
+ {_ports('t', 8)}
113
+ input signed [5:0] final_threshold,
114
+ output person_present
115
+ );
116
+ wire [5:0] count_pos =
117
+ {_popcount(0, 20, lambda i: f't{i:02d}')};
118
+
119
+ wire [5:0] count_neg =
120
+ {_popcount(20, 40, lambda i: f't{i:02d}')};
121
+
122
+ wire signed [6:0] diff = {{1'b0, count_pos}} - {{1'b0, count_neg}};
123
+ assign person_present = diff > final_threshold;
124
+ endmodule
125
+ '''
126
+
127
+
128
+ def emit_popcount_folded(thresholds, final_threshold: int, quant_scale: int) -> str:
129
+ title = 'Popcount-reformulated 1-parameter person classifier, thresholds baked in.'
130
+ return HEADER.format(title=title) + f'''//
131
+ // Per-dim thresholds are the calibrated float values scaled by {quant_scale} and rounded.
132
+
133
+ module person_classifier_popcount_folded (
134
+ {_ports('f', 8)}
135
+ output person_present
136
+ );
137
+ localparam signed [7:0] {_threshold_bank(thresholds)};
138
+ localparam signed [5:0] FINAL_T = {final_threshold};
139
+
140
+ wire [5:0] count_pos =
141
+ {_popcount(0, 20, lambda i: f'T{i:02d}')};
142
+
143
+ wire [5:0] count_neg =
144
+ {_popcount(20, 40, lambda i: f'T{i:02d}')};
145
+
146
+ wire signed [6:0] diff = {{1'b0, count_pos}} - {{1'b0, count_neg}};
147
+ assign person_present = diff > FINAL_T;
148
+ endmodule
149
+ '''
150
+
151
+
152
+ def generate(out_dir: Path = None, thresholds_json: Path = None,
153
+ classifier_json: Path = None):
154
+ """Write all four modules; returns the paths written."""
155
+ out_dir = out_dir or HERE / 'rtl'
156
+ cal = read_artifact(thresholds_json or HERE / 'per_dim_thresholds.json')
157
+ classifier = json.loads(
158
+ (classifier_json or HERE / 'classifier.json').read_text())
159
+
160
+ scale = cal['quant_scale']
161
+ per_dim = [p['threshold_int8'] for p in cal['per_dim_thresholds']]
162
+ final_pop = cal['popcount']['final_threshold']
163
+ stage_0_thr = float(classifier['threshold'])
164
+ stage_0_int8 = int(round(stage_0_thr * scale))
165
+
166
+ out_dir.mkdir(parents=True, exist_ok=True)
167
+ written = {
168
+ 'sum.v': emit_sum(),
169
+ 'sum_folded.v': emit_sum_folded(stage_0_int8, stage_0_thr, scale),
170
+ 'popcount.v': emit_popcount(),
171
+ 'popcount_folded.v': emit_popcount_folded(per_dim, final_pop, scale),
172
+ }
173
+ for name, text in written.items():
174
+ (out_dir / name).write_text(text, encoding='utf-8')
175
+ return [out_dir / n for n in written]
176
+
177
+
178
+ if __name__ == '__main__':
179
+ ap = argparse.ArgumentParser(description=__doc__)
180
+ ap.add_argument('--out', type=Path, default=None)
181
+ args = ap.parse_args()
182
+ for path in generate(args.out):
183
+ print(f'wrote {path}')
synth.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Synthesize the four decision variants with nosis and write circuit.json.
2
+
3
+ python synth.py
4
+
5
+ nosis is a pure-Python SystemVerilog to Lattice ECP5 synthesizer. Counts are
6
+ LUT4s, carry cells and slices on that device. Adder trees land on the carry
7
+ chain, so CCU2C rather than LUT4 is the resource that moves with the form of the
8
+ decision, and `bound` records which resource limits each variant.
9
+ """
10
+ import argparse
11
+ import re
12
+ import subprocess
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ sys.path.insert(0, str(Path(__file__).resolve().parent)) # repo root, for `common`
17
+ from common import read_artifact, write_artifact # noqa: E402
18
+
19
+ HERE = Path(__file__).resolve().parent
20
+ NOSIS_ROOT = Path(r'D:\nosis')
21
+ VARIANTS = {
22
+ 'sum': ('person_classifier_1p', 'runtime input'),
23
+ 'sum_folded': ('person_classifier_sum_folded', 'baked'),
24
+ 'popcount': ('person_classifier_popcount', 'runtime inputs'),
25
+ 'popcount_folded': ('person_classifier_popcount_folded', 'baked'),
26
+ }
27
+
28
+
29
+ def synth_one(src: Path, top: str, build: Path, nosis_root: Path):
30
+ build.mkdir(parents=True, exist_ok=True)
31
+ r = subprocess.run(
32
+ [sys.executable, '-m', 'nosis', str(src), '--top', top, '--stats',
33
+ '-o', str(build / f'{top}.json')],
34
+ cwd=str(nosis_root), capture_output=True, text=True)
35
+ if r.returncode != 0:
36
+ raise SystemExit(f'nosis failed on {src.name}:\n'
37
+ f'{r.stdout[-2000:]}{r.stderr[-2000:]}')
38
+ (build / f'{top}.log').write_text(r.stdout, encoding='utf-8')
39
+
40
+ def grab(pattern, cast=int):
41
+ m = re.search(pattern, r.stdout)
42
+ return cast(m.group(1)) if m else None
43
+
44
+ def text(pattern):
45
+ m = re.search(pattern, r.stdout)
46
+ return m.group(1) if m else None
47
+
48
+ return {'slices': grab(r'Slices:\s+(\d+)'), 'lut4': grab(r'LUTs:\s+(\d+)'),
49
+ 'ccu2c': grab(r'CCU2C:\s+(\d+)'), 'ffs': grab(r'FFs:\s+(\d+)'),
50
+ 'bound': text(r'Bound:\s+(\S+)'),
51
+ 'critical_path_ns': grab(r'Critical path delay:\s+([\d.]+)', float),
52
+ 'device': text(r'Device:\s+(\S+)')}
53
+
54
+
55
+ def main():
56
+ ap = argparse.ArgumentParser(description=__doc__)
57
+ ap.add_argument('--rtl', type=Path, default=HERE / 'rtl')
58
+ ap.add_argument('--build', type=Path, default=HERE / 'build')
59
+ ap.add_argument('--nosis', type=Path, default=NOSIS_ROOT)
60
+ ap.add_argument('--out', type=Path, default=HERE / 'circuit.json')
61
+ args = ap.parse_args()
62
+
63
+ accuracy = read_artifact(args.out)['accuracy']
64
+ variants = {}
65
+ print(f"{'variant':>18}{'slices':>8}{'LUT4':>7}{'CCU2C':>7}{'bound':>7}{'ns':>8}")
66
+ for name, (top, thresholds) in VARIANTS.items():
67
+ src = args.rtl / f'{name}.v'
68
+ if not src.exists():
69
+ raise SystemExit(f'{src} missing; run rtl_gen.py first')
70
+ s = synth_one(src, top, args.build, args.nosis)
71
+ s.update({'rtl': f'rtl/{name}.v', 'thresholds': thresholds})
72
+ variants[name] = s
73
+ print(f'{name:>18}{s["slices"]:>8}{s["lut4"]:>7}{s["ccu2c"]:>7}'
74
+ f'{s["bound"]:>7}{s["critical_path_ns"]:>8.2f}', flush=True)
75
+
76
+ device = next((v['device'] for v in variants.values() if v['device']), None)
77
+ write_artifact(args.out, {'variants': variants, 'accuracy': accuracy},
78
+ generator='synth.py',
79
+ tool='nosis', target={'family': 'ecp5', 'device': device},
80
+ inputs='40 signed INT8 feature channels at the classifier dims',
81
+ note='LUT4, carry and slice counts on an ECP5, not abstract gates')
82
+ print(f'\n[done] wrote {args.out}')
83
+
84
+
85
+ if __name__ == '__main__':
86
+ main()
tests/conftest.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared fixtures and helpers for the consistency suite."""
2
+ import json
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ REPO = Path(__file__).resolve().parents[1]
9
+ sys.path.insert(0, str(REPO))
10
+
11
+
12
+ def load(rel: str) -> dict:
13
+ """Parse a repo-relative JSON file."""
14
+ return json.loads((REPO / rel).read_text(encoding='utf-8'))
15
+
16
+
17
+ @pytest.fixture(scope='session')
18
+ def repo() -> Path:
19
+ return REPO
20
+
21
+
22
+ @pytest.fixture(scope='session')
23
+ def classifier() -> dict:
24
+ return load('classifier.json')
25
+
26
+
27
+ @pytest.fixture(scope='session')
28
+ def tight() -> dict:
29
+ return load('classifier_tight_fpr.json')
tests/test_artifacts.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Every committed artifact matches the schema its generator declares."""
2
+ import pytest
3
+
4
+ from common.artifacts import REGISTRY
5
+ from conftest import REPO, load
6
+
7
+ # Artifacts whose producing sweep was never committed, so provenance.generator
8
+ # is null. A new entry here is a new gap and has to be added deliberately.
9
+ KNOWN_GAPS = {
10
+ 'eval_tight_fpr.json',
11
+ 'discovery/dim_selection.json',
12
+ 'discovery/dim48_characterization.json',
13
+ 'discovery/prop_specificity.json',
14
+ 'discovery/prop_image_manifest.json',
15
+ 'discovery/variant_leaderboard.json',
16
+ }
17
+
18
+
19
+ @pytest.mark.parametrize('rel', sorted(REGISTRY))
20
+ def test_artifact_exists(rel):
21
+ assert (REPO / rel).exists(), f'{rel} is registered but missing'
22
+
23
+
24
+ @pytest.mark.parametrize('rel', sorted(REGISTRY))
25
+ def test_artifact_has_provenance(rel):
26
+ doc = load(rel)
27
+ assert 'provenance' in doc, f'{rel} has no provenance block'
28
+ assert next(iter(doc)) == 'provenance', f'{rel} does not open with its provenance block'
29
+
30
+
31
+ @pytest.mark.parametrize('rel', sorted(r for r in REGISTRY if REGISTRY[r].payload_keys))
32
+ def test_artifact_payload_keys(rel):
33
+ spec = REGISTRY[rel]
34
+ got = tuple(k for k in load(rel) if k != 'provenance')
35
+ assert got == spec.payload_keys, f'{rel} payload keys {got} != declared {spec.payload_keys}'
36
+
37
+
38
+ @pytest.mark.parametrize('rel', sorted(REGISTRY))
39
+ def test_artifact_generator(rel):
40
+ spec = REGISTRY[rel]
41
+ generator = load(rel)['provenance']['generator']
42
+ assert generator in (spec.generator, None), \
43
+ f'{rel} claims generator {generator!r}, registry says {spec.generator!r}'
44
+
45
+
46
+ @pytest.mark.parametrize('rel', sorted(REGISTRY))
47
+ def test_artifact_pool(rel):
48
+ spec = REGISTRY[rel]
49
+ pool = load(rel)['provenance'].get('pool')
50
+ if spec.pool is not None and pool is not None:
51
+ assert pool == spec.pool, f'{rel} names pool {pool!r}, registry says {spec.pool!r}'
52
+
53
+
54
+ def test_known_gaps_are_exactly_the_ungenerated_artifacts():
55
+ ungenerated = {rel for rel in REGISTRY
56
+ if load(rel)['provenance']['generator'] is None}
57
+ assert ungenerated == KNOWN_GAPS
58
+
59
+
60
+ def test_classifier_hashes_are_current():
61
+ """Every artifact naming a classifier config records that file's current hash."""
62
+ from common.artifacts import sha256_of
63
+ for rel in sorted(REGISTRY):
64
+ p = load(rel)['provenance']
65
+ if 'classifier' not in p:
66
+ continue
67
+ assert p['classifier_sha256'] == sha256_of(REPO / p['classifier']), \
68
+ f'{rel} pins a stale hash for {p["classifier"]}'
tests/test_dims.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """The 40 classifier dims agree everywhere they are written down."""
2
+ from safetensors.torch import load_file
3
+
4
+ from conftest import REPO, load
5
+
6
+ N_POS = 20
7
+ N_NEG = 20
8
+
9
+
10
+ def test_baseline_counts(classifier):
11
+ assert len(classifier['pos_dims']) == N_POS
12
+ assert len(classifier['neg_dims']) == N_NEG
13
+ assert not set(classifier['pos_dims']) & set(classifier['neg_dims'])
14
+ assert classifier['fixed_parameters']['dim_indices'] == N_POS + N_NEG
15
+ assert classifier['free_parameters'] == 1
16
+
17
+
18
+ def test_tight_fpr_extends_the_baseline(classifier, tight):
19
+ assert tight['pos_dims'] == classifier['pos_dims']
20
+ assert tight['neg_dims_original'] == classifier['neg_dims']
21
+ assert tight['neg_dims'] == tight['neg_dims_original'] + tight['neg_dims_extra']
22
+ assert len(set(tight['neg_dims'])) == len(tight['neg_dims'])
23
+ assert tight['fixed_parameters']['dim_indices'] == len(tight['pos_dims']) + len(tight['neg_dims'])
24
+
25
+
26
+ def test_leaderboard_prefixes_match_the_shipped_lists(classifier):
27
+ board = load('discovery/variant_leaderboard.json')
28
+ assert board['top_pos_dims_30'][:N_POS] == classifier['pos_dims']
29
+ assert board['top_neg_dims_30'][:N_NEG] == classifier['neg_dims']
30
+
31
+
32
+ def test_tight_fpr_extra_dims_come_from_the_prop_sweep(tight):
33
+ sweep = load('discovery/prop_specificity.json')
34
+ k = len(tight['neg_dims_extra'])
35
+ row = next(s for s in sweep['sweeps'] if s['extra_neg_k'] == k)
36
+ assert row['added_dims'] == tight['neg_dims_extra']
37
+ assert abs(row['threshold'] - tight['threshold']) < 1e-6
38
+
39
+
40
+ def test_per_dim_thresholds_index_the_same_dims(classifier):
41
+ cal = load('per_dim_thresholds.json')
42
+ entries = cal['per_dim_thresholds']
43
+ assert [e['dim_global'] for e in entries] == classifier['pos_dims'] + classifier['neg_dims']
44
+ assert [e['dim_index_in_40'] for e in entries] == list(range(N_POS + N_NEG))
45
+ assert [e['is_pos'] for e in entries] == [True] * N_POS + [False] * N_NEG
46
+
47
+
48
+ def test_quantized_thresholds_match_their_floats():
49
+ cal = load('per_dim_thresholds.json')
50
+ scale = cal['quant_scale']
51
+ for e in cal['per_dim_thresholds']:
52
+ assert e['threshold_int8'] == round(e['threshold'] * scale)
53
+ assert -128 <= e['threshold_int8'] <= 127
54
+
55
+
56
+ def test_safetensors_agree_with_the_json_configs(classifier, tight):
57
+ for name, c in (('classifier', classifier), ('classifier_tight_fpr', tight)):
58
+ t = load_file(str(REPO / f'{name}.safetensors'))
59
+ assert t['pos_dims'].tolist() == c['pos_dims']
60
+ assert t['neg_dims'].tolist() == c['neg_dims']
61
+ assert t['retained_dims'].tolist() == c['pos_dims'] + c['neg_dims']
62
+ assert abs(float(t['threshold'][0]) - c['threshold']) < 1e-4
63
+ w = t['retained_weight'][0].tolist()
64
+ assert w == [1.0] * len(c['pos_dims']) + [-1.0] * len(c['neg_dims'])
tests/test_head.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """The fused linear head is identical to the ternary sum it replaces."""
2
+ import sys
3
+
4
+ import torch
5
+
6
+ from common import D, score
7
+ from conftest import REPO
8
+
9
+ sys.path.insert(0, str(REPO))
10
+ from head import FusedClassifier # noqa: E402
11
+
12
+
13
+ def test_fused_head_equals_ternary_score(classifier):
14
+ torch.manual_seed(0)
15
+ model = FusedClassifier.from_config(None, REPO / 'classifier.json').eval()
16
+ pooled = torch.randn(64, D) * 4.0
17
+ fused, present = model.head(pooled)
18
+ direct = score(pooled, classifier['pos_dims'], classifier['neg_dims'])
19
+ assert torch.allclose(fused, direct, atol=1e-4)
20
+ assert torch.equal(present, direct > classifier['threshold'])
21
+
22
+
23
+ def test_only_the_threshold_is_learnable(classifier):
24
+ model = FusedClassifier.from_config(None, REPO / 'classifier.json')
25
+ learnable = [n for n, p in model.named_parameters() if p.requires_grad]
26
+ assert learnable == ['threshold']
27
+ assert sum(p.numel() for p in model.parameters()) == 1
28
+
29
+
30
+ def test_tight_fpr_head_reads_its_own_dim_count(tight):
31
+ model = FusedClassifier.from_config(
32
+ None, REPO / 'classifier_tight_fpr.json')
33
+ assert model.retained_dims.numel() == len(tight['pos_dims']) + len(tight['neg_dims'])
34
+ torch.manual_seed(1)
35
+ pooled = torch.randn(32, D) * 4.0
36
+ fused, _ = model.head(pooled)
37
+ direct = score(pooled, tight['pos_dims'], tight['neg_dims'])
38
+ assert torch.allclose(fused, direct, atol=1e-4)
tests/test_rtl.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """The generated RTL carries the calibrated constants and computes the decision.
2
+
3
+ The constant and structural checks run everywhere. The simulation checks run
4
+ when Icarus Verilog is available, either on PATH or via the IVERILOG and VVP
5
+ environment variables.
6
+ """
7
+ import os
8
+ import re
9
+ import shutil
10
+ import subprocess
11
+
12
+ import pytest
13
+
14
+ from conftest import REPO, load
15
+
16
+ RTL = REPO / 'rtl'
17
+ N_DIMS = 40
18
+ N_POS = 20
19
+ N_VECTORS = 512
20
+
21
+
22
+ def tool(name, env_var):
23
+ return os.environ.get(env_var) or shutil.which(name)
24
+
25
+
26
+ IVERILOG = tool('iverilog', 'IVERILOG')
27
+ VVP = tool('vvp', 'VVP')
28
+ needs_sim = pytest.mark.skipif(not (IVERILOG and VVP),
29
+ reason='Icarus Verilog not found on PATH or in IVERILOG/VVP')
30
+
31
+
32
+ @pytest.fixture(scope='module')
33
+ def calibration():
34
+ return load('per_dim_thresholds.json')
35
+
36
+
37
+ def source(name: str) -> str:
38
+ return (RTL / f'{name}.v').read_text(encoding='utf-8')
39
+
40
+
41
+ # ---------------- constants and structure ----------------
42
+
43
+ def test_baked_per_dim_thresholds_match_the_calibration(calibration):
44
+ text = source('popcount_folded')
45
+ baked = {int(i): int(v) for i, v in re.findall(r'T(\d\d) =\s*(-?\d+)', text)}
46
+ assert len(baked) == N_DIMS
47
+ for e in calibration['per_dim_thresholds']:
48
+ assert baked[e['dim_index_in_40']] == e['threshold_int8']
49
+
50
+
51
+ def test_baked_final_threshold_matches_the_calibration(calibration):
52
+ text = source('popcount_folded')
53
+ assert int(re.search(r'FINAL_T = (-?\d+);', text).group(1)) == \
54
+ calibration['popcount']['final_threshold']
55
+
56
+
57
+ def test_sum_folded_threshold_is_the_quantized_threshold(classifier, calibration):
58
+ text = source('sum_folded')
59
+ baked = int(re.search(r"FINAL_T = 16'sd(-?\d+);", text).group(1))
60
+ assert baked == round(classifier['threshold'] * calibration['quant_scale'])
61
+
62
+
63
+ @pytest.mark.parametrize('name,threshold', [('popcount', 't{:02d}'),
64
+ ('popcount_folded', 'T{:02d}')])
65
+ def test_each_channel_is_compared_against_its_own_threshold(name, threshold):
66
+ text = source(name)
67
+ pairs = re.findall(r'\(f(\d\d) > (\w+)\)', text)
68
+ assert len(pairs) == N_DIMS
69
+ for feature, thr in pairs:
70
+ assert thr == threshold.format(int(feature))
71
+ assert [int(f) for f, _ in pairs] == list(range(N_DIMS))
72
+
73
+
74
+ @pytest.mark.parametrize('name', ['popcount', 'popcount_folded'])
75
+ def test_no_bit_select_on_a_vector(name):
76
+ """Bit-select lowering is unreliable at index 0 in the synthesis backend.
77
+
78
+ The comparisons are summed inline instead, so nothing here depends on it.
79
+ """
80
+ text = '\n'.join(l for l in source(name).splitlines()
81
+ if not l.strip().startswith('//'))
82
+ assert not re.search(r'\b(pos|neg)_bits\b', text)
83
+
84
+
85
+ @pytest.mark.parametrize('name', ['sum', 'sum_folded', 'popcount', 'popcount_folded'])
86
+ def test_every_feature_port_is_declared_once(name):
87
+ declared = re.findall(r'\bf(\d\d)\b(?=[,)\s])', source(name).split(');')[0])
88
+ assert sorted(set(declared)) == [f'{i:02d}' for i in range(N_DIMS)]
89
+
90
+
91
+ # ---------------- simulation ----------------
92
+
93
+ def vectors(n: int, seed: int = 0):
94
+ """Deterministic signed-INT8 feature vectors, uniform over the input range."""
95
+ import random
96
+ rng = random.Random(seed)
97
+ return [[rng.randint(-128, 127) for _ in range(N_DIMS)] for _ in range(n)]
98
+
99
+
100
+ def popcount_boundary_vectors(thresholds, k: int, n: int, seed: int = 0):
101
+ """Vectors placing the count difference within two of K, on the per-dim thresholds.
102
+
103
+ An `off` channel sits at exactly its threshold, so `>` must reject it.
104
+ """
105
+ import random
106
+ rng = random.Random(seed)
107
+ out = []
108
+ while len(out) < n:
109
+ diff = k + rng.randint(-2, 2)
110
+ n_neg = rng.randint(0, max(0, N_POS - abs(diff)))
111
+ n_pos = diff + n_neg
112
+ if not (0 <= n_pos <= N_POS and 0 <= n_neg <= N_POS):
113
+ continue
114
+ pos_on = [True] * n_pos + [False] * (N_POS - n_pos)
115
+ neg_on = [True] * n_neg + [False] * (N_POS - n_neg)
116
+ rng.shuffle(pos_on)
117
+ rng.shuffle(neg_on)
118
+ on = pos_on + neg_on
119
+ vec = [max(-128, min(127, t + 1)) if on[i] else max(-128, min(127, t))
120
+ for i, t in enumerate(thresholds)]
121
+ out.append(vec)
122
+ return out
123
+
124
+
125
+ def additive_boundary_vectors(threshold: int, n: int, seed: int = 0):
126
+ """Vectors whose signed sum lands within a few counts of the comparator threshold."""
127
+ import random
128
+ rng = random.Random(seed)
129
+ out = []
130
+ while len(out) < n:
131
+ vec = [rng.randint(-20, 20) for _ in range(N_DIMS)]
132
+ target = threshold + rng.randint(-4, 4)
133
+ # Solve f00 so that sum(pos) - sum(neg) hits the target exactly.
134
+ vec[0] = target - (sum(vec[1:N_POS]) - sum(vec[N_POS:]))
135
+ if -128 <= vec[0] <= 127:
136
+ out.append(vec)
137
+ return out
138
+
139
+
140
+ def additive_reference(vec, threshold):
141
+ return (sum(vec[:N_POS]) - sum(vec[N_POS:])) > threshold
142
+
143
+
144
+ def popcount_reference(vec, thresholds, k):
145
+ pos = sum(1 for i in range(N_POS) if vec[i] > thresholds[i])
146
+ neg = sum(1 for i in range(N_POS, N_DIMS) if vec[i] > thresholds[i])
147
+ return (pos - neg) > k
148
+
149
+
150
+ def literal(value: int, width: int) -> str:
151
+ return f"-{width}'sd{-value}" if value < 0 else f"{width}'sd{value}"
152
+
153
+
154
+ def make_testbench(module: str, extra_ports: str, n: int) -> str:
155
+ features = ', '.join(f'f{i:02d}' for i in range(N_DIMS))
156
+ connections = ',\n '.join(f'.f{i:02d}(f{i:02d})' for i in range(N_DIMS))
157
+ slices = '\n '.join(
158
+ f'f{i:02d} = vecs[i][{319 - 8 * i}:{312 - 8 * i}];' for i in range(N_DIMS))
159
+ return f'''`timescale 1ns/1ps
160
+ module tb;
161
+ reg [319:0] vecs [0:{n - 1}];
162
+ reg signed [7:0] {features};
163
+ wire out;
164
+ integer i;
165
+ {module} dut (
166
+ {connections},{extra_ports}
167
+ .person_present(out));
168
+ initial begin
169
+ $readmemh("vectors.hex", vecs);
170
+ for (i = 0; i < {n}; i = i + 1) begin
171
+ {slices}
172
+ #1;
173
+ $display("%b", out);
174
+ end
175
+ $finish;
176
+ end
177
+ endmodule
178
+ '''
179
+
180
+
181
+ def run_sim(tmp_path, rtl_name, module, extra_ports, vecs):
182
+ tmp_path.mkdir(parents=True, exist_ok=True)
183
+ hexfile = tmp_path / 'vectors.hex'
184
+ hexfile.write_text('\n'.join(
185
+ ''.join(f'{v & 0xFF:02x}' for v in vec) for vec in vecs) + '\n')
186
+ (tmp_path / 'tb.v').write_text(make_testbench(module, extra_ports, len(vecs)))
187
+ subprocess.run([IVERILOG, '-g2005', '-o', 'tb.vvp',
188
+ str(RTL / f'{rtl_name}.v'), 'tb.v'],
189
+ cwd=tmp_path, check=True, capture_output=True)
190
+ out = subprocess.run([VVP, 'tb.vvp'], cwd=tmp_path, check=True,
191
+ capture_output=True, text=True).stdout
192
+ bits = [line.strip() for line in out.splitlines() if line.strip() in ('0', '1')]
193
+ assert len(bits) == len(vecs), f'{module}: {len(bits)} results for {len(vecs)} vectors'
194
+ return [b == '1' for b in bits]
195
+
196
+
197
+ @needs_sim
198
+ def test_sum_matches_the_additive_reference(tmp_path, classifier, calibration):
199
+ thr = round(classifier['threshold'] * calibration['quant_scale'])
200
+ vecs = vectors(N_VECTORS) + additive_boundary_vectors(thr, N_VECTORS)
201
+ got = run_sim(tmp_path, 'sum', 'person_classifier_1p',
202
+ f'\n .threshold({literal(thr, 16)}),', vecs)
203
+ assert got == [additive_reference(v, thr) for v in vecs]
204
+
205
+
206
+ @needs_sim
207
+ def test_sum_folded_matches_the_additive_reference(tmp_path, classifier, calibration):
208
+ thr = round(classifier['threshold'] * calibration['quant_scale'])
209
+ vecs = vectors(N_VECTORS) + additive_boundary_vectors(thr, N_VECTORS)
210
+ got = run_sim(tmp_path, 'sum_folded', 'person_classifier_sum_folded', '', vecs)
211
+ assert got == [additive_reference(v, thr) for v in vecs]
212
+
213
+
214
+ @needs_sim
215
+ def test_popcount_matches_the_popcount_reference(tmp_path, calibration):
216
+ thresholds = [e['threshold_int8'] for e in calibration['per_dim_thresholds']]
217
+ k = calibration['popcount']['final_threshold']
218
+ ports = ''.join(f'\n .t{i:02d}({literal(t, 8)}),'
219
+ for i, t in enumerate(thresholds))
220
+ ports += f'\n .final_threshold({literal(k, 6)}),'
221
+ vecs = vectors(N_VECTORS) + popcount_boundary_vectors(thresholds, k, N_VECTORS)
222
+ got = run_sim(tmp_path, 'popcount', 'person_classifier_popcount', ports, vecs)
223
+ assert got == [popcount_reference(v, thresholds, k) for v in vecs]
224
+
225
+
226
+ @needs_sim
227
+ def test_popcount_folded_matches_the_popcount_reference(tmp_path, calibration):
228
+ thresholds = [e['threshold_int8'] for e in calibration['per_dim_thresholds']]
229
+ k = calibration['popcount']['final_threshold']
230
+ vecs = vectors(N_VECTORS) + popcount_boundary_vectors(thresholds, k, N_VECTORS)
231
+ got = run_sim(tmp_path, 'popcount_folded', 'person_classifier_popcount_folded',
232
+ '', vecs)
233
+ assert got == [popcount_reference(v, thresholds, k) for v in vecs]
234
+
235
+
236
+ @needs_sim
237
+ def test_folding_a_threshold_does_not_change_the_decision(tmp_path, classifier,
238
+ calibration):
239
+ """Each runtime-threshold module agrees with its baked counterpart."""
240
+ thr = round(classifier['threshold'] * calibration['quant_scale'])
241
+ vecs = vectors(N_VECTORS, seed=1) + additive_boundary_vectors(thr, N_VECTORS, seed=1)
242
+ runtime = run_sim(tmp_path / 'a', 'sum', 'person_classifier_1p',
243
+ f'\n .threshold({literal(thr, 16)}),', vecs)
244
+ baked = run_sim(tmp_path / 'b', 'sum_folded', 'person_classifier_sum_folded', '', vecs)
245
+ assert runtime == baked
verify.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Score a classifier config over a named pool and write its eval artifact.
2
+
3
+ python verify.py # baseline on VAL5000
4
+ python verify.py --classifier classifier_tight_fpr.json
5
+ python verify.py --pool CALIB1000
6
+
7
+ Writes eval.json for the baseline config and eval_tight_fpr.json for the
8
+ tight-FPR one, unless --out says otherwise.
9
+ """
10
+ import argparse
11
+ import json
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ import torch
16
+
17
+ sys.path.insert(0, str(Path(__file__).resolve().parent)) # repo root, for `common`
18
+ from common import BACKBONE, device, f1_at, load_pool, score_pool, write_artifact # noqa: E402
19
+ from common.models import load_backbone # noqa: E402
20
+ from common.pools import VAL5000, by_name # noqa: E402
21
+
22
+ HERE = Path(__file__).resolve().parent
23
+
24
+
25
+ def out_path_for(classifier: Path) -> Path:
26
+ """classifier.json -> eval.json; classifier_tight_fpr.json -> eval_tight_fpr.json."""
27
+ suffix = classifier.stem[len('classifier'):]
28
+ return classifier.parent / f'eval{suffix}.json'
29
+
30
+
31
+ def main():
32
+ ap = argparse.ArgumentParser(description=__doc__)
33
+ ap.add_argument('--classifier', type=Path, default=HERE / 'classifier.json')
34
+ ap.add_argument('--backbone', default=BACKBONE)
35
+ ap.add_argument('--pool', default=VAL5000.name)
36
+ ap.add_argument('--out', type=Path, default=None)
37
+ args = ap.parse_args()
38
+
39
+ dev = device()
40
+ pool = by_name(args.pool)
41
+ c = json.loads(args.classifier.read_text())
42
+ print(f'[init] loading {args.backbone}', flush=True)
43
+ backbone = load_backbone(args.backbone).to(dev)
44
+
45
+ print(f'[pool] {pool.name}', flush=True)
46
+ loaded = load_pool(pool, dev)
47
+ pos = torch.tensor(c['pos_dims'], dtype=torch.long, device=dev)
48
+ neg = torch.tensor(c['neg_dims'], dtype=torch.long, device=dev)
49
+
50
+ scores, _ = score_pool(backbone, loaded, pos, neg)
51
+ m = f1_at(scores, loaded.labels, c['threshold'])
52
+ print(f'[verify] F1={m.f1:.4f} P={m.precision:.4f} R={m.recall:.4f}', flush=True)
53
+
54
+ path = args.out or out_path_for(args.classifier)
55
+ write_artifact(
56
+ path, {'metrics': {k: round(v, 4) if k != 'threshold' else v
57
+ for k, v in m.asdict().items()}},
58
+ generator='verify.py', classifier=args.classifier,
59
+ pool_info=loaded.provenance(),
60
+ task='image-level person presence (binary)',
61
+ protocol='live backbone forward at 768 px, no feature caching')
62
+ print(f'[done] wrote {path}', flush=True)
63
+
64
+
65
+ if __name__ == '__main__':
66
+ main()