Viewing is open to everyone. Downloading and starring need a GitHub sign-in. Sign in with GitHub

train_locator_ranker.py

v0.1.0-build.85 Latest Commit ede38f5

Size
2.8 KB
Lines
51
Language
python
SHA-256
07928adddc59…
View on GitHub

Sign in with GitHub to download. Each download is recorded with your GitHub username.

1#!/usr/bin/env python3
2"""Generate the tiny on-device CKey border-component ranker.
3
4This is deliberately a candidate ranker, not a decoder. It is trained from
5deterministic synthetic component measurements, while the native geometry
6fit and RS/CRC checks remain the authority for accepting a CKey. Replace or
7augment the synthetic distributions with labelled camera captures as they are
8collected; the output stays a dependency-free Swift source file.
9"""
10import argparse
11from pathlib import Path
12import numpy as np
13from sklearn.linear_model import LogisticRegression
14
15parser = argparse.ArgumentParser(description="Train the on-device CKey candidate ranker.")
16parser.add_argument("--features", type=Path, help="CSV emitted by train_synthetic_locator.py")
17args = parser.parse_args()
18
19if args.features:
20 rows = np.loadtxt(args.features, delimiter=",", skiprows=1)
21 if rows.ndim != 2 or rows.shape[1] != 4:
22 raise SystemExit("feature CSV must contain label,aspect_error,fill_ratio,relative_side")
23 labels, features = rows[:, 0], rows[:, 1:]
24else:
25 # Kept only as a dependency-light smoke path. Production updates should
26 # use the encoded/rasterized corpus generated by train_synthetic_locator.
27 rng = np.random.default_rng(0xC1E7)
28 count = 12_000
29 positive = np.column_stack((
30 np.abs(rng.normal(.035, .035, count)),
31 np.clip(rng.normal(.09, .045, count), .005, .28),
32 np.clip(rng.normal(.32, .16, count), .06, .72),
33 ))
34 negative = np.column_stack((
35 np.abs(rng.normal(.42, .32, count)),
36 np.clip(rng.beta(2, 3, count) * .8, .01, .95),
37 rng.uniform(.01, .98, count),
38 ))
39 features = np.vstack((positive, negative))
40 labels = np.r_[np.ones(count), np.zeros(count)]
41# Keep the generated Swift feature contract identical to CameraScanner: the
42# source CSV carries a relative side, while the on-device ranker converts it
43# to distance from the useful 0.32 frame fraction.
44features = features.copy()
45features[:, 2] = np.abs(features[:, 2] - .32)
46model = LogisticRegression(C=3, max_iter=2000, random_state=0).fit(features, labels)
47weights = model.coef_[0]
48bias = model.intercept_[0]
49target = Path(__file__).parents[1] / "ios/CKeyReader/LocatorRanker.swift"
50target.write_text(f'''// Generated by tools/train_locator_ranker.py. Do not hand-tune.\n\nimport Foundation\n\nenum LocatorRanker {{\n private static let weights = [{weights[0]:.8f}, {weights[1]:.8f}, {weights[2]:.8f}]\n private static let bias = {bias:.8f}\n\n static func score(aspectError: Double, fillRatio: Double, relativeSide: Double) -> Double {{\n let sideError = abs(relativeSide - 0.32)\n let z = bias + weights[0] * aspectError + weights[1] * fillRatio + weights[2] * sideError\n return 1.0 / (1.0 + exp(-z))\n }}\n}}\n''')
51print(f"wrote {target}; training accuracy={model.score(features, labels):.3f}; examples={len(labels)}")