our_knp/augment/tta_sim.py
int 45880f1d19 Stroke-detector + pooling features prototype, robustness check in feature mode
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:26:07 +03:00

75 lines
2.7 KiB
Python

"""Test-time shift voting in the offline AltAI simulator.
Each image is fed several times with small shifts; votes of all copies are summed.
Also measures robustness: the test images themselves are shifted / thickened first.
usage: python tta_sim.py <dump_dir> <images.bin> <labels.target> --first 30000 --test 2000 [--features]
With --features the network was trained on stroke features: <images.bin> must hold raw pixels, distortions
are applied to the pixels, then features are computed (no shift voting in this mode).
"""
import argparse
import sys
import numpy as np
sys.path.insert(0, "/mnt/c/knp/viz")
import altai_sim as s # noqa: E402
sys.path.insert(0, "/mnt/c/knp/features")
import stroke_features # noqa: E402
CROSS = [(0, 0), (0, 1), (0, -1), (1, 0), (-1, 0)]
SQUARE = [(dy, dx) for dy in (-1, 0, 1) for dx in (-1, 0, 1)]
def shift(a, dy, dx):
return np.roll(np.roll(a, dy, -2), dx, -1)
def grow(a):
return np.maximum.reduce([shift(a, dy, dx) for dy, dx in CROSS])
def thin(a):
return np.minimum.reduce([shift(a, dy, dx) for dy, dx in CROSS])
def evaluate(w, thr, imgs, labels, offsets, inhib):
sim = s.Sim(w, thr, inhib)
correct = silent = 0
for img, lab in zip(imgs, labels):
votes = sum(sim.run_image(shift(img, dy, dx).reshape(-1))[0] for dy, dx in offsets)
p = s.predict(votes)
correct += p == lab
silent += p < 0
return 100 * correct / len(labels), 100 * silent / len(labels)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("dump")
ap.add_argument("images")
ap.add_argument("labels")
ap.add_argument("--first", type=int, default=30000)
ap.add_argument("--test", type=int, default=2000)
ap.add_argument("--inhib", type=float, default=255)
ap.add_argument("--features", action="store_true", help="network input is stroke features of the pixels")
a = ap.parse_args()
w, thr = s.load_dump(a.dump)
imgs, labels = s.load_images(a.images, a.labels, a.first, a.test)
imgs = imgs.reshape(-1, 28, 28).astype(np.int32)
variants = {"as is": imgs, "shift 2px": shift(imgs, 0, 2), "thicker 1px": grow(imgs), "thinner 1px": thin(imgs)}
votings = [("1 copy", [(0, 0)])] + ([] if a.features else [("5 copies (cross)", CROSS), ("9 copies (3x3)", SQUARE)])
for vote_name, offsets in votings:
row = []
for name, x in variants.items():
x = x.clip(0, 255).astype(np.uint8)
if a.features:
x = stroke_features.features(x).reshape(-1, 28, 28)
acc, sil = evaluate(w, thr, x, labels, offsets, a.inhib)
row.append(f"{name} {acc:5.1f}%")
print(f"{vote_name:17s} | " + " | ".join(row), flush=True)
if __name__ == "__main__":
main()