GeoguessrAngular commited on
Commit
3e6c42e
·
1 Parent(s): 2e93310

feat(bot): weaker bots sometimes pick another plausible option (spread by level)

Browse files
Files changed (2) hide show
  1. app.py +4 -1
  2. region.py +27 -11
app.py CHANGED
@@ -244,8 +244,11 @@ async def do_guess(images: list[UploadFile] = File(None),
244
  k = min(nloc, max(5, int(round(f * nloc))))
245
  sel = np.where(rr < k)[0]
246
  re_, rl_, rn_ = re_[sel], rl_[sel], rn_[sel]
 
 
 
247
  result = regionlib.predict(list(emb), re_, rl_, rn_,
248
- M["country_slug"], M["country_name"])
249
  elif M.get("type") == "head":
250
  result = atlaslib.predict(list(emb), M["W"], M["b"], M["classes"],
251
  STATE["centroids"], STATE["cfg"], STATE["priors"],
 
244
  k = min(nloc, max(5, int(round(f * nloc))))
245
  sel = np.where(rr < k)[0]
246
  re_, rl_, rn_ = re_[sel], rl_[sel], rn_[sel]
247
+ # "human mistake" spread by level: easy often takes another option,
248
+ # medium rarely, hard never (best geo-medoid).
249
+ spread = 0.6 if skill <= 1.5 else 0.2 if skill <= 2.5 else 0.0
250
  result = regionlib.predict(list(emb), re_, rl_, rn_,
251
+ M["country_slug"], M["country_name"], spread=spread)
252
  elif M.get("type") == "head":
253
  result = atlaslib.predict(list(emb), M["W"], M["b"], M["classes"],
254
  STATE["centroids"], STATE["cfg"], STATE["priors"],
region.py CHANGED
@@ -16,30 +16,46 @@ def load_region(path):
16
  return emb, z["lat"].astype(np.float64), z["lng"].astype(np.float64)
17
 
18
 
19
- def predict(embs, ref_emb, ref_lat, ref_lng, country_slug, country_name, k=5):
20
- """embs: list of (D,) query-frame embeddings. Returns a scoreable guess dict
21
- (same shape the bot/guess.py emits): predicted lat/lon from the nearest
22
- reference image, confidence from cosine similarity."""
 
 
 
 
 
23
  Q = np.stack([e / (np.linalg.norm(e) + 1e-8) for e in embs]) # (F, D)
24
  sims = Q @ ref_emb.T # (F, N)
25
  fi, ni = np.unravel_index(int(np.argmax(sims)), sims.shape)
26
  best = float(sims[fi, ni])
27
 
28
- # Top-k across all frames geo-medoid (robust to a single odd neighbour).
29
  flat = sims.reshape(-1)
30
- kk = min(k, flat.shape[0])
31
- top = np.argpartition(-flat, kk - 1)[:kk]
 
32
  cols = (top % ref_emb.shape[0])
33
  cla, cln = ref_lat[cols], ref_lng[cols]
 
 
 
34
  R = 6371.0088; p = np.pi / 180.0
35
- dsum = np.empty(len(cols))
36
- for i in range(len(cols)):
37
- a = (np.sin((cla - cla[i]) * p / 2) ** 2
38
- + np.cos(cla[i] * p) * np.cos(cla * p) * np.sin((cln - cln[i]) * p / 2) ** 2)
39
  dsum[i] = (2 * R * np.arcsin(np.sqrt(np.clip(a, 0, 1)))).sum()
40
  m = int(dsum.argmin())
41
  lat, lon = float(cla[m]), float(cln[m])
42
 
 
 
 
 
 
 
 
43
  conf = round(max(0.0, min(1.0, best)), 3)
44
  return {
45
  "country": country_slug,
 
16
  return emb, z["lat"].astype(np.float64), z["lng"].astype(np.float64)
17
 
18
 
19
+ def predict(embs, ref_emb, ref_lat, ref_lng, country_slug, country_name, k=5, spread=0.0):
20
+ """embs: list of (D,) query-frame embeddings. Returns a scoreable guess dict.
21
+
22
+ `spread` (0..1) is the "make a human-like mistake" knob for weaker bots: the
23
+ candidate pool is widened and, with probability `spread`, the bot takes one of
24
+ the other (still visually-similar) options instead of the best geo-medoid. So
25
+ an easy bot sometimes confidently picks a wrong-but-plausible spot. spread=0
26
+ (hard) always returns the best geo-medoid.
27
+ """
28
  Q = np.stack([e / (np.linalg.norm(e) + 1e-8) for e in embs]) # (F, D)
29
  sims = Q @ ref_emb.T # (F, N)
30
  fi, ni = np.unravel_index(int(np.argmax(sims)), sims.shape)
31
  best = float(sims[fi, ni])
32
 
33
+ # Candidate pool: top-k for the best guess, widened by spread for mistakes.
34
  flat = sims.reshape(-1)
35
+ poolK = min(flat.shape[0], max(k, int(round(k + spread * 35))))
36
+ top = np.argpartition(-flat, poolK - 1)[:poolK]
37
+ top = top[np.argsort(-flat[top])] # best-first
38
  cols = (top % ref_emb.shape[0])
39
  cla, cln = ref_lat[cols], ref_lng[cols]
40
+
41
+ # geo-medoid over the best k (robust to a single odd neighbour).
42
+ kk = min(k, len(cols))
43
  R = 6371.0088; p = np.pi / 180.0
44
+ dsum = np.empty(kk)
45
+ for i in range(kk):
46
+ a = (np.sin((cla[:kk] - cla[i]) * p / 2) ** 2
47
+ + np.cos(cla[i] * p) * np.cos(cla[:kk] * p) * np.sin((cln[:kk] - cln[i]) * p / 2) ** 2)
48
  dsum[i] = (2 * R * np.arcsin(np.sqrt(np.clip(a, 0, 1)))).sum()
49
  m = int(dsum.argmin())
50
  lat, lon = float(cla[m]), float(cln[m])
51
 
52
+ # Weaker bots: sometimes grab a different, still-plausible option (ranks k..pool).
53
+ if spread > 0 and len(cols) > kk:
54
+ rng = np.random.default_rng()
55
+ if rng.random() < spread:
56
+ j = int(rng.integers(kk, len(cols)))
57
+ lat, lon = float(cla[j]), float(cln[j])
58
+
59
  conf = round(max(0.0, min(1.0, best)), 3)
60
  return {
61
  "country": country_slug,