Spaces:
Sleeping
Sleeping
Commit ·
3e6c42e
1
Parent(s): 2e93310
feat(bot): weaker bots sometimes pick another plausible option (spread by level)
Browse files
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 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 29 |
flat = sims.reshape(-1)
|
| 30 |
-
|
| 31 |
-
top = np.argpartition(-flat,
|
|
|
|
| 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(
|
| 36 |
-
for i in range(
|
| 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,
|