mirror of
https://github.com/wassname/moral-maps.git
synced 2026-09-23 13:30:23 +08:00
Measure how far each model sits from a human cluster, in cluster SDs
Lets a model be described as a 2.9 sigma member of the West instead of "somewhere west of Silicon Valley". Signed per-axis z says which way and how far on a named axis; Mahalanobis says how odd the placement is overall, and uses the cluster covariance because the zones are elongated and tilted. Reads the committed coords in wvs_model_ci.md rather than the coord cache, so it reruns offline without re-querying seventeen models. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
01e9026641
commit
f6c22aca30
+37
-1
@@ -41,7 +41,7 @@ from datasets import load_dataset
|
||||
from transformers import AutoModelForCausalLM, AutoTokenizer
|
||||
|
||||
from moralmaps import maps
|
||||
from moralmaps.zones import zones_for, zone_of
|
||||
from moralmaps.zones import zones_for, zone_of, IW_MACRO
|
||||
from moralmaps.instrument import Instrument, InstrItem
|
||||
from moralmaps.read import read_items, resolve_answer_ids
|
||||
from moralmaps.read_api import read_items_rated
|
||||
@@ -165,6 +165,39 @@ def model_coord_ci(psamples: dict[str, np.ndarray], resolved: dict[str, list[dic
|
||||
return x, y, float(np.std(bx)), float(np.std(by))
|
||||
|
||||
|
||||
def cluster_outlier_sd(countries: list[str], P: np.ndarray, models: dict[str, tuple],
|
||||
min_n: int = 8) -> list[tuple]:
|
||||
"""How odd each model looks as a member of each human macro-zone, in cluster SDs.
|
||||
|
||||
Two readings per (model, zone). The signed per-axis z says which way and how far on one named
|
||||
axis, so `+2.9` on secular-rational reads as "2.9 sigma more secular-rational than the average
|
||||
member of this zone". The Mahalanobis distance says how odd the placement is overall, using the
|
||||
zone's own 2x2 covariance; it is the honest scalar because the zones are elongated and tilted
|
||||
(the West runs diagonally), so a model far along a zone's own long axis is less of an outlier
|
||||
than a plain z suggests. Zones under min_n countries are skipped: a 2x2 covariance from a handful
|
||||
of points is mostly noise."""
|
||||
by_zone: dict[str, list[int]] = {}
|
||||
for i, c in enumerate(countries):
|
||||
z = zone_of(c)
|
||||
if z is not None:
|
||||
by_zone.setdefault(IW_MACRO[z], []).append(i)
|
||||
out = []
|
||||
for zone, idx in sorted(by_zone.items()):
|
||||
if len(idx) < min_n:
|
||||
continue
|
||||
Z = P[idx]
|
||||
mu, sd = Z.mean(0), Z.std(0, ddof=1)
|
||||
# ridge keeps the inverse finite if a zone is near-degenerate on one axis
|
||||
S = np.cov(Z.T) + 1e-6 * np.eye(2)
|
||||
Sinv = np.linalg.inv(S)
|
||||
for name, v in models.items():
|
||||
d = np.array([v[0], v[1]]) - mu
|
||||
out.append((name.replace(" (rated)", ""), zone, len(idx),
|
||||
float(d[0] / sd[0]), float(d[1] / sd[1]),
|
||||
float(np.sqrt(d @ Sinv @ d))))
|
||||
return out
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--local-model", default="Qwen/Qwen3-0.6B")
|
||||
@@ -282,6 +315,9 @@ def main() -> None:
|
||||
Path(args.out).with_name("wvs_model_ci.md").write_text(table + "\n")
|
||||
logger.info("model coords + 95% CI (widest first):\n" + table)
|
||||
|
||||
# scripts/wvs_outlier_table.py turns wvs_model_ci.md into the zone-SD outlier table. It reads the
|
||||
# committed coords rather than the cache, so it reruns offline without paying for 17 models again.
|
||||
|
||||
# Render through the SHARED value-map renderer (same one the instrument value maps use): pole
|
||||
# signposts through the human median, 4 auto-selected zone hulls, auto-placed labels, model stars.
|
||||
_, emph = zones_for(countries)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""How much of a cultural outlier each model is, in the SDs of a human macro-zone.
|
||||
|
||||
Turns the committed model coordinates (docs/img/wvs/wvs_model_ci.md, written by wvs_map.py) into
|
||||
docs/img/wvs/wvs_model_outlier_sd.md. Reads the table rather than the coord cache so it reruns
|
||||
offline, without re-querying seventeen models. The human coordinates are recomputed from WVS here,
|
||||
the same way wvs_map.py computes them.
|
||||
|
||||
uv run python scripts/wvs_outlier_table.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
from loguru import logger
|
||||
from tabulate import tabulate
|
||||
|
||||
from moralmaps.iw_axes import resolve_items
|
||||
from wvs_map import cluster_outlier_sd, human_axis_scores, load_wvs_all
|
||||
|
||||
IMG = Path(__file__).resolve().parent.parent / "docs" / "img" / "wvs"
|
||||
|
||||
|
||||
def read_model_coords(path: Path) -> dict[str, tuple]:
|
||||
"""(x, y) per model from the pipe-table wvs_map.py writes. Columns: model, x, y, x CI, y CI."""
|
||||
models = {}
|
||||
for line in path.read_text().splitlines():
|
||||
cells = [c.strip() for c in line.strip().strip("|").split("|")]
|
||||
if len(cells) < 3 or cells[0] in ("model", "") or set(cells[1]) <= set(":- "):
|
||||
continue
|
||||
models[cells[0]] = (float(cells[1]), float(cells[2]))
|
||||
return models
|
||||
|
||||
|
||||
def main() -> None:
|
||||
models = read_model_coords(IMG / "wvs_model_ci.md")
|
||||
countries, P = human_axis_scores(resolve_items(load_wvs_all()))
|
||||
logger.info(f"{len(models)} models against {len(countries)} human societies")
|
||||
|
||||
rows = cluster_outlier_sd(countries, P, models)
|
||||
west_z = {n: z for n, zone, _, _, z, _ in rows if zone == "West"}
|
||||
rows.sort(key=lambda r: (-west_z.get(r[0], 0.0), r[0], r[1]))
|
||||
table = tabulate(rows, headers=["model", "zone", "n countries",
|
||||
"z self-expr", "z secular", "Mahalanobis"],
|
||||
tablefmt="pipe", floatfmt="+.2f")
|
||||
(IMG / "wvs_model_outlier_sd.md").write_text(table + "\n")
|
||||
logger.info("model distance from human zones, in zone SDs:\n" + table)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user