From 8a358f3d652b3da2ea6ea55079bf3d5f9094c770 Mon Sep 17 00:00:00 2001 From: ErenAta16 Date: Tue, 23 Jun 2026 19:45:04 +0300 Subject: [PATCH 01/14] chore: move API keys to environment --- .env.example | 2 ++ .gitignore | 7 +++++++ code/lm_utils.py | 32 +++++++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 .env.example create mode 100644 .gitignore diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..fbd323e --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +OPENAI_API_KEY= +DEEPSEEK_API_KEY= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a4bfa48 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.env +__pycache__/ +*.pyc +results/ +batch_results/ +*.csv +_sample_rows.json diff --git a/code/lm_utils.py b/code/lm_utils.py index d787efc..2f80776 100644 --- a/code/lm_utils.py +++ b/code/lm_utils.py @@ -10,6 +10,22 @@ import wikipedia as wp from transformers import AutoModelForCausalLM, AutoTokenizer + +def _require_env(name): + value = os.environ.get(name) + if not value: + raise ValueError( + f"{name} environment variable is required. Set it in your environment." + ) + return value + + +def _resolve_deepseek_api_key(deep_seek_api=None): + if deep_seek_api: + return deep_seek_api + return _require_env("DEEPSEEK_API_KEY") + + def llm_init(model_name): global device global model @@ -53,13 +69,16 @@ def llm_init(model_name): model = AutoModelForCausalLM.from_pretrained("CohereLabs/aya-expanse-8b", device_map="auto", torch_dtype=torch.bfloat16) tokenizer = AutoTokenizer.from_pretrained("CohereLabs/aya-expanse-8b") if model_name == "deepseek": - client = OpenAI(api_key="API Keys", base_url="https://api.deepseek.com") + client = OpenAI( + api_key=_require_env("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com", + ) if model_name == "chatgpt": - client = OpenAI(api_key="API Keys") # Fill in your API key + client = OpenAI(api_key=_require_env("OPENAI_API_KEY")) -def llm_response(prompt, model_name, num_responses, language=None, save_path = None, deep_seek_api = "sk-fe66b828e09e45a3ba34ec9b3caf05ee",temperature = 0.7, max_new_tokens = 100): +def llm_response(prompt, model_name, num_responses, language=None, save_path = None, deep_seek_api = None,temperature = 0.7, max_new_tokens = 100): language_instruction_map = { "en": "Please answer in English.", "zh": "请用中文作答。", @@ -129,7 +148,7 @@ def llm_response(prompt, model_name, num_responses, language=None, save_path = N if model_name == "chatgpt": responses = [] - client = OpenAI(api_key="API Keys") # Fill in your API key + client = OpenAI(api_key=_require_env("OPENAI_API_KEY")) for i in range(num_responses): response = client.chat.completions.create( @@ -145,7 +164,10 @@ def llm_response(prompt, model_name, num_responses, language=None, save_path = N if model_name == "deepseek": responses = [] # for backward compatibility, you can still use `https://api.deepseek.com/v1` as `base_url`. - client = OpenAI(api_key=deep_seek_api, base_url="https://api.deepseek.com") + client = OpenAI( + api_key=_resolve_deepseek_api_key(deep_seek_api), + base_url="https://api.deepseek.com", + ) for i in range(num_responses): From 3d2114d574798ed3cd6cadef224ac62d4fd6efe2 Mon Sep 17 00:00:00 2001 From: ErenAta16 Date: Tue, 23 Jun 2026 19:45:04 +0300 Subject: [PATCH 02/14] fix: restore entity extraction client initialization --- code/entity_extraction.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/code/entity_extraction.py b/code/entity_extraction.py index a79d3a5..e3343d8 100644 --- a/code/entity_extraction.py +++ b/code/entity_extraction.py @@ -132,7 +132,12 @@ def parse_batch_results(filename): # **Complete get_key_words function** def get_key_words(response_text_dict, category): - client = OpenAI(api_key )= # Replace with actual API Key) + api_key = os.environ.get("OPENAI_API_KEY") + if not api_key: + raise ValueError( + "OPENAI_API_KEY environment variable is required for entity extraction." + ) + client = OpenAI(api_key=api_key) file_id = prepare_batch_requests(response_text_dict, category, client) # Generate JSONL file batch_id = submit_batch(client, file_id) # Submit batch request From 22b1d9ed640d61035918892a79248e7817849853 Mon Sep 17 00:00:00 2001 From: ErenAta16 Date: Tue, 23 Jun 2026 19:45:04 +0300 Subject: [PATCH 03/14] feat: add metric reproducibility tools --- README.md | 59 ++++++- code/data_loading.py | 328 +++++++++++++++++++++++++++++++++++++ code/metrics.py | 166 +++++++++++++++++++ code/run_metrics.py | 236 ++++++++++++++++++++++++++ code/validate_fidelity.py | 310 +++++++++++++++++++++++++++++++++++ docs/DEVIATIONS.md | 50 ++++++ docs/FIDELITY_REPORT.md | 25 +++ requirements.txt | 12 ++ tests/test_data_loading.py | 68 ++++++++ tests/test_metrics.py | 181 ++++++++++++++++++++ 10 files changed, 1432 insertions(+), 3 deletions(-) create mode 100644 code/data_loading.py create mode 100644 code/metrics.py create mode 100644 code/run_metrics.py create mode 100644 code/validate_fidelity.py create mode 100644 docs/DEVIATIONS.md create mode 100644 docs/FIDELITY_REPORT.md create mode 100644 requirements.txt create mode 100644 tests/test_data_loading.py create mode 100644 tests/test_metrics.py diff --git a/README.md b/README.md index 230e3f9..2b35b2a 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,13 @@ We have released the MAKIEval dataset on Hugging Face: The released dataset currently contains: -* 🤖 13 LLMs +* 🤖 **7 LLMs** (see paper Table 1) * 🌐 13 Languages -* 🗺️ Multiple Countries and Regions +* 🗺️ 19 Countries and Regions * 🎭 6 Cultural Domains +> **Note on scale:** The Hugging Face release contains approximately **5.25M rows**. The paper reports 85.8 million generated texts; at the response level the expected order of magnitude is ≈6M (1,716 prompts × 500 responses × 7 models). The difference may reflect aggregation, filtering, or release packaging — we report HF counts here rather than restating the paper figure. + ### Cultural Domains * 🍽️ Food @@ -55,15 +57,66 @@ entities ```text code/ - analysis_*.py + analysis_*.py # topic-specific Wikidata linking + data_loading.py # Hugging Face dataset loader entity_extraction.py + lm_utils.py + metrics.py # granularity, diversity, specificity, consensus prompt_construct.py run_experiment.py + run_metrics.py # CLI for metric computation + validate_fidelity.py # paper-facing fidelity checks meta_info/ country.json name.json prompt.json + +tests/ + test_data_loading.py + test_metrics.py + +docs/ + DEVIATIONS.md + FIDELITY_REPORT.md +``` + +--- + +## 🔁 Reproduce (metrics) + +> ⚠️ **Use `--full` for paper-comparable numbers. The default 5,000-row limit is only a quick smoke test; Diversity and Consensus in this mode are not comparable to the paper values.** + +### 1. Install + +```bash +python -m venv .venv +source .venv/bin/activate # Windows: .venv\Scripts\activate +pip install -r requirements.txt +``` + +### 2. Compute metrics from the public dataset + +No API key is required for `run_metrics.py`; API-backed generation and entity +extraction still require `OPENAI_API_KEY` / `DEEPSEEK_API_KEY` in the +environment. + +```bash +python code/run_metrics.py --model Qwen2.5-7B-Instruct --topic beverage --language ar --seed 42 --limit 5000 +``` + +Outputs are written to `results/metrics/`: + +* `group_metrics.csv` — granularity, diversity, culture specificity per (model, topic, language, country) +* `culture_consensus.csv` — pairwise Jaccard scores across languages +* `parse_report.json` — entity JSON parse statistics + +`run_metrics.py` defaults to a 5,000-row limit for quick checks. Add `--full` to process all rows matching the selected filters. + +### 3. Run tests + +```bash +pytest -q tests/ ``` --- diff --git a/code/data_loading.py b/code/data_loading.py new file mode 100644 index 0000000..27a0c5a --- /dev/null +++ b/code/data_loading.py @@ -0,0 +1,328 @@ +"""Load and clean the Raoyuan/MAKIEval Hugging Face dataset.""" + +from __future__ import annotations + +import json +from dataclasses import dataclass +from functools import lru_cache +from itertools import product +from typing import Any + +import pandas as pd +import requests + +EXCLUDED_ENTITY_TYPES = frozenset( + {"place", "person_name", "listener_name", "reader_name"} +) + +DEFAULT_COLUMNS = [ + "model", + "topic", + "language", + "country_region", + "prompt", + "generated_text", + "entities", +] + +GROUP_COLUMNS = ["model", "topic", "language", "country_region"] + +COUNTRY_ALIASES = { + "us": "united states", + "usa": "united states", + "u.s.": "united states", + "united states": "united states", + "uk": "united kingdom", + "u.k.": "united kingdom", + "united kingdom": "united kingdom", + "uae": "united arab emirates", + "united arab emirates": "united arab emirates", +} + +TOPIC_ALIASES = { + "books": "book", +} + + +@dataclass +class ParseReport: + total_rows: int + parse_errors: int + empty_entity_rows: int + excluded_entity_count: int + + +def parse_entities_field(raw_entities: Any) -> tuple[list[dict[str, Any]], bool]: + """ + Parse the entities column into a list of dicts. + + Returns (entities, ok) where ok is False when the raw value could not be parsed. + """ + if raw_entities is None: + return [], False + + if isinstance(raw_entities, list): + return raw_entities, True + + if isinstance(raw_entities, str): + text = raw_entities.strip() + if not text: + return [], True + try: + parsed = json.loads(text) + except json.JSONDecodeError: + return [], False + if isinstance(parsed, list): + return parsed, True + return [], False + + return [], False + + +def filter_entities_for_metrics( + entities: list[dict[str, Any]], +) -> tuple[list[dict[str, Any]], int]: + """Drop entity types excluded from metric computation.""" + kept: list[dict[str, Any]] = [] + excluded = 0 + for entity in entities: + entity_type = str(entity.get("entity_type", "")).lower() + if entity_type in EXCLUDED_ENTITY_TYPES: + excluded += 1 + continue + kept.append(entity) + return kept, excluded + + +def clean_entities_column(df: pd.DataFrame) -> tuple[pd.DataFrame, ParseReport]: + """Parse entities JSON and attach metric-eligible entity lists.""" + parsed_entities: list[list[dict[str, Any]]] = [] + parse_errors = 0 + empty_rows = 0 + excluded_total = 0 + + for raw in df["entities"]: + entities, ok = parse_entities_field(raw) + if not ok: + parse_errors += 1 + parsed_entities.append([]) + continue + if not entities: + empty_rows += 1 + filtered, excluded = filter_entities_for_metrics(entities) + excluded_total += excluded + parsed_entities.append(filtered) + + result = df.copy() + result["entities_parsed"] = parsed_entities + report = ParseReport( + total_rows=len(df), + parse_errors=parse_errors, + empty_entity_rows=empty_rows, + excluded_entity_count=excluded_total, + ) + return result, report + + +def _filter_tuples(filters: dict[str, str] | None) -> list[tuple[str, str, str]] | None: + if not filters: + return None + return [(column, "==", value) for column, value in filters.items()] + + +def normalize_filters(filters: dict[str, str] | None) -> dict[str, str] | None: + """Normalize user-facing filter aliases to Hugging Face column values.""" + if not filters: + return None + + normalized = dict(filters) + if "topic" in normalized: + topic = normalized["topic"].strip().lower() + normalized["topic"] = TOPIC_ALIASES.get(topic, topic) + if "language" in normalized: + normalized["language"] = normalized["language"].strip().lower() + if "country_region" in normalized: + country = " ".join(normalized["country_region"].strip().lower().split()) + normalized["country_region"] = COUNTRY_ALIASES.get(country, country) + return normalized + + +def _hf_parquet_urls(split: str) -> list[str]: + response = requests.get( + "https://datasets-server.huggingface.co/parquet", + params={"dataset": "Raoyuan/MAKIEval"}, + timeout=30, + ) + response.raise_for_status() + payload = response.json() + return [ + item["url"] + for item in payload.get("parquet_files", []) + if item.get("config") == "default" and item.get("split") == split + ] + + +@lru_cache(maxsize=8) +def _hf_group_column_values(split: str) -> dict[str, list[str]]: + response = requests.get( + "https://datasets-server.huggingface.co/statistics", + params={ + "dataset": "Raoyuan/MAKIEval", + "config": "default", + "split": split, + }, + timeout=30, + ) + response.raise_for_status() + payload = response.json() + values: dict[str, list[str]] = {} + for item in payload.get("statistics", []): + column_name = item.get("column_name") + if column_name not in GROUP_COLUMNS: + continue + frequencies = item.get("column_statistics", {}).get("frequencies", {}) + values[column_name] = sorted(frequencies) + return values + + +def _load_from_parquet( + split: str, + filters: dict[str, str] | None, + limit: int | None, + columns: list[str] | None = None, +) -> pd.DataFrame: + if columns is None: + columns = DEFAULT_COLUMNS + frames: list[pd.DataFrame] = [] + + for shard in iter_makieval_parquet_frames( + split=split, + filters=filters, + columns=columns, + ): + if shard.empty: + continue + frames.append(shard) + if limit is not None and sum(len(frame) for frame in frames) >= limit: + break + + if not frames: + return pd.DataFrame(columns=columns) + + df = pd.concat(frames, ignore_index=True) + if limit is not None: + df = df.head(limit) + return df + + +def iter_makieval_parquet_frames( + split: str = "train", + filters: dict[str, str] | None = None, + columns: list[str] | None = None, +): + """Yield filtered MAKIEval parquet shards as pandas DataFrames.""" + if columns is None: + columns = DEFAULT_COLUMNS + normalized_filters = normalize_filters(filters) + parquet_filters = _filter_tuples(normalized_filters) + + for url in _hf_parquet_urls(split): + yield pd.read_parquet( + url, + engine="pyarrow", + columns=columns, + filters=parquet_filters, + ) + + +def load_expected_group_keys( + split: str = "train", + filters: dict[str, str] | None = None, + exact: bool = False, +) -> list[tuple[str, str, str, str]]: + """Return expected group keys for the given filters. + + By default this uses HF Dataset Viewer statistics to build a fast group + cross-product. Set exact=True for a slower filtered parquet scan of present keys. + """ + normalized_filters = normalize_filters(filters) + if not exact: + column_values = _hf_group_column_values(split) + values_by_column = [] + for column in GROUP_COLUMNS: + if normalized_filters and normalized_filters.get(column): + values_by_column.append([normalized_filters[column]]) + else: + values_by_column.append(column_values.get(column, [])) + return [tuple(group) for group in product(*values_by_column)] + + groups: set[tuple[str, str, str, str]] = set() + for shard in iter_makieval_parquet_frames( + split=split, + filters=normalized_filters, + columns=GROUP_COLUMNS, + ): + if shard.empty: + continue + unique = shard.drop_duplicates(GROUP_COLUMNS).sort_values(GROUP_COLUMNS) + groups.update( + tuple(row) + for row in unique[GROUP_COLUMNS].itertuples(index=False, name=None) + ) + return sorted(groups) + + +def load_makieval_dataset( + split: str = "train", + streaming: bool | None = None, + filters: dict[str, str] | None = None, + limit: int | None = None, +) -> tuple[pd.DataFrame, ParseReport]: + """ + Load Raoyuan/MAKIEval from Hugging Face and return a cleaned DataFrame. + + filters may include model, topic, language, country_region keys matching dataset columns. + Uses filtered parquet reads when filters are provided, and streaming otherwise. + """ + normalized_filters = normalize_filters(filters) + if filters: + try: + df = _load_from_parquet(split=split, filters=normalized_filters, limit=limit) + if df.empty: + report = ParseReport(0, 0, 0, 0) + return df, report + return clean_entities_column(df) + except Exception: + # Fall back to datasets streaming if Dataset Viewer/parquet is unavailable. + pass + + from datasets import load_dataset + + if streaming is None: + streaming = True + + dataset = load_dataset("Raoyuan/MAKIEval", split=split, streaming=streaming) + + rows: list[dict[str, Any]] = [] + for idx, row in enumerate(dataset): + if normalized_filters: + if normalized_filters.get("model") and row["model"] != normalized_filters["model"]: + continue + if normalized_filters.get("topic") and row["topic"] != normalized_filters["topic"]: + continue + if normalized_filters.get("language") and row["language"] != normalized_filters["language"]: + continue + if ( + normalized_filters.get("country_region") + and row["country_region"] != normalized_filters["country_region"] + ): + continue + rows.append(dict(row)) + if limit is not None and len(rows) >= limit: + break + + df = pd.DataFrame(rows) + if df.empty: + report = ParseReport(0, 0, 0, 0) + return df, report + return clean_entities_column(df) diff --git a/code/metrics.py b/code/metrics.py new file mode 100644 index 0000000..7fea354 --- /dev/null +++ b/code/metrics.py @@ -0,0 +1,166 @@ +"""MAKIEval cultural-awareness metrics (paper Section 4).""" + +from __future__ import annotations + +from typing import Any, Iterable + +# Granularity: 1 = specific entity, 0 = category-level entity (Appendix B.2 / Section 4.1). +CATEGORY_ENTITY_TYPES = frozenset( + { + "dish_category", + "ingredient_category", + "beverage_category", + "book_genre", + "music_genre", + "fashion_style", + "vehicle_type", + } +) + +SPECIFIC_ENTITY_TYPES = frozenset( + { + "dish_name", + "dish", + "specific_ingredient", + "drink_name", + "book_title", + "song_name", + "clothing_type", + "mode_of_transport", + "artist_name", + "author_name", + } +) + +# Country aliases for matching target regions in culture specificity. +COUNTRY_ALIASES: dict[str, set[str]] = { + "united states": {"united states", "us", "usa", "u.s.", "america"}, + "united kingdom": {"united kingdom", "uk", "u.k.", "britain", "great britain"}, + "south korea": {"south korea", "korea", "republic of korea"}, + "united arab emirates": {"united arab emirates", "uae"}, + "taiwan": {"taiwan", "republic of china"}, + "china": {"china", "people's republic of china", "prc"}, + "mexico": {"mexico"}, + "spain": {"spain"}, + "argentina": {"argentina"}, + "germany": {"germany"}, + "iran": {"iran"}, + "india": {"india"}, + "italy": {"italy"}, + "japan": {"japan"}, + "thailand": {"thailand"}, + "turkey": {"turkey", "türkiye"}, + "canada": {"canada"}, + "australia": {"australia"}, + "nigeria": {"nigeria"}, +} + + +def _normalize_country(name: str) -> str: + return " ".join(name.strip().lower().replace("_", " ").split()) + + +def _entity_type(entity: dict[str, Any]) -> str: + return str(entity.get("entity_type", "")).lower() + + +def _entity_qid(entity: dict[str, Any]) -> str | None: + qid = entity.get("qid") + if qid is None or qid == "" or str(qid).upper() == "NA": + return None + return str(qid) + + +def _granularity_score(entity: dict[str, Any]) -> float | None: + entity_type = _entity_type(entity) + if entity_type in CATEGORY_ENTITY_TYPES: + return 0.0 + if entity_type in SPECIFIC_ENTITY_TYPES: + return 1.0 + # Unknown types are excluded from the average (paper uses typed cultural entities). + return None + + +def granularity(entities: Iterable[dict[str, Any]]) -> float: + """ + Eq. (1): Granularity = (1/|E|) * sum(Gran(e)). + + Specific cultural entities score 1; category-level entities score 0. + Entities with unknown types are excluded from |E|. + """ + scores = [s for s in (_granularity_score(e) for e in entities) if s is not None] + if not scores: + return 0.0 + return sum(scores) / len(scores) + + +def diversity(entities: Iterable[dict[str, Any]]) -> int: + """ + Section 4.2: count of unique Wikidata QIDs in entity set E. + + Rows with null/NA QIDs are excluded from diversity (QID-required metric). + """ + qids = {_entity_qid(e) for e in entities} + qids.discard(None) + return len(qids) + + +def _countries_match(target_country: str, origin_country: str) -> bool: + target_norm = _normalize_country(target_country) + origin_norm = _normalize_country(origin_country) + + if target_norm == origin_norm: + return True + + for canonical, aliases in COUNTRY_ALIASES.items(): + if target_norm in aliases or target_norm == canonical: + if origin_norm in aliases or origin_norm == canonical: + return True + return False + + +def culture_specificity( + entities: Iterable[dict[str, Any]], + target_country: str, + country_lookup: dict[str, str], +) -> float: + """ + Section 4.3: proportion of entities whose origin country matches target_country. + + country_lookup maps QID -> English country label from Wikidata (P495/P17). + Entities without a QID or without lookup metadata are excluded from the denominator. + """ + eligible = [] + matches = 0 + for entity in entities: + qid = _entity_qid(entity) + if not qid: + continue + origin = country_lookup.get(qid) + if not origin or origin == "NA": + continue + eligible.append(entity) + if _countries_match(target_country, origin): + matches += 1 + + if not eligible: + return 0.0 + return matches / len(eligible) + + +def culture_consensus(qids_lang_a: Iterable[str], qids_lang_b: Iterable[str]) -> float: + """ + Eq. (2) in Section 4.4: Jaccard similarity |Q_A ∩ Q_B| / |Q_A ∪ Q_B|. + """ + set_a = {qid for qid in qids_lang_a if qid} + set_b = {qid for qid in qids_lang_b if qid} + union = set_a | set_b + if not union: + return 0.0 + return len(set_a & set_b) / len(union) + + +def collect_qids(entities: Iterable[dict[str, Any]]) -> set[str]: + qids = {_entity_qid(e) for e in entities} + qids.discard(None) + return qids diff --git a/code/run_metrics.py b/code/run_metrics.py new file mode 100644 index 0000000..2687406 --- /dev/null +++ b/code/run_metrics.py @@ -0,0 +1,236 @@ +"""CLI to compute MAKIEval metrics from the Hugging Face dataset.""" + +from __future__ import annotations + +import argparse +import json +import os +import random +import sys +from collections import defaultdict +from pathlib import Path + +import numpy as np +import pandas as pd + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from data_loading import GROUP_COLUMNS, load_expected_group_keys, load_makieval_dataset +from metrics import ( + collect_qids, + culture_consensus, + culture_specificity, + diversity, + granularity, +) + + +def _set_seed(seed: int) -> None: + random.seed(seed) + np.random.seed(seed) + + +def _aggregate_group_metrics( + group_df: pd.DataFrame, + country_lookup: dict[str, str], +) -> dict: + all_entities: list[dict] = [] + for entities in group_df["entities_parsed"]: + all_entities.extend(entities) + + target_country = str(group_df["country_region"].iloc[0]) + return { + "granularity": granularity(all_entities), + "diversity": diversity(all_entities), + "culture_specificity": culture_specificity( + all_entities, target_country, country_lookup + ), + "entity_count": len(all_entities), + "unique_qids": len(collect_qids(all_entities)), + } + + +def _compute_consensus_rows(df: pd.DataFrame) -> list[dict]: + """Pairwise culture consensus across languages for each (model, topic, country).""" + rows = [] + group_cols = ["model", "topic", "country_region"] + for (model, topic, country), group in df.groupby(group_cols): + lang_qids: dict[str, set[str]] = {} + for language, lang_df in group.groupby("language"): + qids: set[str] = set() + for entities in lang_df["entities_parsed"]: + qids.update(collect_qids(entities)) + lang_qids[language] = qids + + languages = sorted(lang_qids) + for i, lang_a in enumerate(languages): + for lang_b in languages[i + 1 :]: + rows.append( + { + "model": model, + "topic": topic, + "country_region": country, + "language_a": lang_a, + "language_b": lang_b, + "culture_consensus": culture_consensus( + lang_qids[lang_a], lang_qids[lang_b] + ), + } + ) + return rows + + +def _add_run_metadata( + df: pd.DataFrame, + is_partial_sample: bool, + n_rows_used: int, + warning: str, +) -> pd.DataFrame: + result = df.copy() + result["is_partial_sample"] = is_partial_sample + result["n_rows_used"] = n_rows_used + result["warning"] = warning + return result + + +def _computed_group_keys(metrics_df: pd.DataFrame) -> set[tuple[str, str, str, str]]: + if metrics_df.empty: + return set() + return { + tuple(row) + for row in metrics_df[GROUP_COLUMNS].itertuples(index=False, name=None) + } + + +def main() -> None: + parser = argparse.ArgumentParser(description="Compute MAKIEval metrics from HF data.") + parser.add_argument("--model", default=None, help="Filter by model name") + parser.add_argument("--topic", default=None, help="Filter by topic") + parser.add_argument("--language", default=None, help="Filter by language") + parser.add_argument("--country", default=None, help="Filter by country_region") + parser.add_argument( + "--limit", + type=int, + default=5000, + help="Optional row limit for quick reproducibility checks (default: 5000)", + ) + parser.add_argument( + "--full", + action="store_true", + help="Process all rows matching the filters instead of the default quick limit", + ) + parser.add_argument("--seed", type=int, default=42, help="Random seed") + parser.add_argument( + "--output-dir", + default="results/metrics", + help="Directory for CSV/JSON outputs", + ) + parser.add_argument( + "--country-lookup", + default=None, + help="Optional JSON file mapping QID -> origin country label", + ) + args = parser.parse_args() + _set_seed(args.seed) + if args.full: + args.limit = None + + filters = { + k: v + for k, v in { + "model": args.model, + "topic": args.topic, + "language": args.language, + "country_region": args.country, + }.items() + if v + } + + df, report = load_makieval_dataset(filters=filters or None, limit=args.limit) + if df.empty: + print("No rows matched the provided filters.") + return + + country_lookup: dict[str, str] = {} + if args.country_lookup and os.path.exists(args.country_lookup): + with open(args.country_lookup, encoding="utf-8") as f: + country_lookup = json.load(f) + + metric_rows = [] + group_cols = GROUP_COLUMNS + for keys, group in df.groupby(group_cols): + if not isinstance(keys, tuple): + keys = (keys,) + metrics = _aggregate_group_metrics(group, country_lookup) + row = dict(zip(group_cols, keys)) + row.update(metrics) + metric_rows.append(row) + + metrics_df = pd.DataFrame(metric_rows) + consensus_df = pd.DataFrame(_compute_consensus_rows(df)) + is_partial_sample = args.limit is not None + warning = "" + if is_partial_sample: + warning = ( + f"WARNING: partial sample ({len(df)} rows); diversity/consensus NOT " + "comparable to paper. Use --full." + ) + print(warning, file=sys.stderr) + + expected_groups = load_expected_group_keys(filters=filters or None, exact=True) + computed_groups = _computed_group_keys(metrics_df) + missing_groups = sorted(set(expected_groups) - computed_groups) + completeness_warning = "" + if len(expected_groups) != len(computed_groups): + completeness_warning = ( + f"WARNING: expected {len(expected_groups)} groups, computed " + f"{len(computed_groups)}; missing: {missing_groups}" + ) + print(completeness_warning, file=sys.stderr) + + combined_warning = " ".join( + message for message in [warning, completeness_warning] if message + ) + metrics_df = _add_run_metadata( + metrics_df, + is_partial_sample=is_partial_sample, + n_rows_used=len(df), + warning=combined_warning, + ) + consensus_df = _add_run_metadata( + consensus_df, + is_partial_sample=is_partial_sample, + n_rows_used=len(df), + warning=combined_warning, + ) + + output_dir = Path(args.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + + metrics_path = output_dir / "group_metrics.csv" + consensus_path = output_dir / "culture_consensus.csv" + report_path = output_dir / "parse_report.json" + + metrics_df.to_csv(metrics_path, index=False) + consensus_df.to_csv(consensus_path, index=False) + report_payload = report.__dict__ | { + "is_partial_sample": is_partial_sample, + "n_rows_used": len(df), + "warning": combined_warning, + "expected_group_count": len(expected_groups), + "computed_group_count": len(computed_groups), + "missing_groups": [list(group) for group in missing_groups], + } + with open(report_path, "w", encoding="utf-8") as f: + json.dump(report_payload, f, indent=2) + + print(f"Wrote {len(metrics_df)} group metric rows to {metrics_path}") + print(f"Wrote {len(consensus_df)} consensus rows to {consensus_path}") + print( + f"Parse report: {report.parse_errors} parse errors / " + f"{report.total_rows} rows" + ) + + +if __name__ == "__main__": + main() diff --git a/code/validate_fidelity.py b/code/validate_fidelity.py new file mode 100644 index 0000000..de71662 --- /dev/null +++ b/code/validate_fidelity.py @@ -0,0 +1,310 @@ +"""Validate MAKIEval metric fidelity against paper-level reference checks.""" + +from __future__ import annotations + +import argparse +import json +import os +import sys +from collections import Counter, defaultdict +from pathlib import Path +from statistics import mean +from typing import Any + +from tqdm import tqdm + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from data_loading import ( # noqa: E402 + GROUP_COLUMNS, + filter_entities_for_metrics, + iter_makieval_parquet_frames, + parse_entities_field, +) +from metrics import collect_qids, culture_consensus # noqa: E402 + +TARGET_DEEPSEEK_SLICE = { + "model": "DeepSeek-V3", + "topic": "book", + "language": "en", + "country_region": "united states", +} + +REFERENCE_NOTES = { + "deepseek_us_book": ( + "Paper Section 6.2: DeepSeek-V3, English, book, US is dominated by " + "To Kill a Mockingbird (~499/500), diversity approx 1." + ), + "diversity": ( + "Paper Table 12 order-of-magnitude: Llama-3.1-8B highest (~36-39), " + "DeepSeek lowest (~14-17)." + ), + "consensus": ( + "Paper Table 3 order-of-magnitude: model averages roughly 0.11-0.21; " + "larger models around 0.20." + ), +} + + +def _group_key(row: Any) -> tuple[str, str, str, str]: + return tuple(getattr(row, column) for column in GROUP_COLUMNS) + + +def _cache_path(cache_dir: Path, full: bool, limit: int | None) -> Path: + suffix = "full" if full else f"limit_{limit}" + return cache_dir / f"fidelity_cache_{suffix}.json" + + +def _serialize_group_qids(group_qids: dict[tuple[str, str, str, str], set[str]]) -> dict[str, list[str]]: + return {"||".join(key): sorted(qids) for key, qids in group_qids.items()} + + +def _deserialize_group_qids(raw: dict[str, list[str]]) -> dict[tuple[str, str, str, str], set[str]]: + return {tuple(key.split("||")): set(qids) for key, qids in raw.items()} + + +def _compute_cache(full: bool, limit: int | None, cache_file: Path) -> dict[str, Any]: + group_qids: dict[tuple[str, str, str, str], set[str]] = defaultdict(set) + deepseek_qid_row_counts: Counter[str] = Counter() + deepseek_rows = 0 + rows_seen = 0 + + columns = GROUP_COLUMNS + ["entities"] + for frame in tqdm( + iter_makieval_parquet_frames(columns=columns), + desc="Reading MAKIEval parquet shards", + unit="shard", + ): + if frame.empty: + continue + + for row in frame.itertuples(index=False): + if not full and limit is not None and rows_seen >= limit: + break + rows_seen += 1 + + entities, ok = parse_entities_field(getattr(row, "entities")) + if not ok: + continue + filtered_entities, _ = filter_entities_for_metrics(entities) + qids = collect_qids(filtered_entities) + + key = _group_key(row) + if dict(zip(GROUP_COLUMNS, key)) == TARGET_DEEPSEEK_SLICE: + deepseek_rows += 1 + deepseek_qid_row_counts.update(qids) + + if not qids: + continue + group_qids[key].update(qids) + + if not full and limit is not None and rows_seen >= limit: + break + + payload = { + "full": full, + "limit": limit, + "rows_seen": rows_seen, + "group_qids": _serialize_group_qids(group_qids), + "deepseek_us_book": { + "rows": deepseek_rows, + "qid_row_counts": dict(deepseek_qid_row_counts), + }, + } + cache_file.parent.mkdir(parents=True, exist_ok=True) + with open(cache_file, "w", encoding="utf-8") as f: + json.dump(payload, f) + return payload + + +def _load_or_compute_cache(args: argparse.Namespace) -> dict[str, Any]: + cache_dir = Path(args.cache_dir) + cache_file = _cache_path(cache_dir, args.full, args.limit) + if cache_file.exists() and not args.force_refresh: + with open(cache_file, encoding="utf-8") as f: + return json.load(f) + return _compute_cache(full=args.full, limit=args.limit, cache_file=cache_file) + + +def _deepseek_us_book_row(payload: dict[str, Any], group_qids: dict[tuple[str, str, str, str], set[str]]) -> dict[str, str]: + target_key = tuple(TARGET_DEEPSEEK_SLICE[column] for column in GROUP_COLUMNS) + qids = group_qids.get(target_key, set()) + diversity_value = len(qids) + + slice_payload = payload.get("deepseek_us_book", {}) + qid_counts = Counter(slice_payload.get("qid_row_counts", {})) + total_rows = int(slice_payload.get("rows", 0)) + dominant_qid = "NA" + dominant_count = 0 + if qid_counts: + dominant_qid, dominant_count = qid_counts.most_common(1)[0] + + dominant_rate = dominant_count / total_rows if total_rows else 0.0 + status = "PASS" if diversity_value <= 2 and dominant_rate >= 0.9 else "MISMATCH" + return { + "metric": "DeepSeek US book dominance", + "slice": "DeepSeek-V3 / book / en / US", + "computed": ( + f"diversity={diversity_value}; dominant={dominant_qid} " + f"{dominant_count}/{total_rows} ({dominant_rate:.1%})" + ), + "paper_reference": REFERENCE_NOTES["deepseek_us_book"], + "status": status, + "comment": "PASS threshold: diversity <= 2 and dominant QID row frequency >= 90%.", + } + + +def _average_diversity_rows(group_qids: dict[tuple[str, str, str, str], set[str]]) -> list[dict[str, str]]: + values_by_model: dict[str, list[int]] = defaultdict(list) + for (model, _topic, _language, _country), qids in group_qids.items(): + values_by_model[model].append(len(qids)) + + averages = {model: mean(values) for model, values in values_by_model.items() if values} + if not averages: + return [] + + highest_model = max(averages, key=averages.get) + lowest_model = min(averages, key=averages.get) + rows = [] + for model, value in sorted(averages.items()): + if model == "Llama-3.1-8B-Instruct": + status = "PASS" if highest_model == model and 25 <= value <= 50 else "MISMATCH" + comment = f"Highest model observed: {highest_model}." + elif model == "DeepSeek-V3": + status = "PASS" if lowest_model == model and value <= 25 else "MISMATCH" + comment = f"Lowest model observed: {lowest_model}." + else: + status = "PASS" if 5 <= value <= 60 else "MISMATCH" + comment = "Broad order-of-magnitude check; no exact per-model target asserted." + + rows.append( + { + "metric": "Average diversity", + "slice": model, + "computed": f"{value:.2f}", + "paper_reference": REFERENCE_NOTES["diversity"], + "status": status, + "comment": comment, + } + ) + return rows + + +def _average_consensus_rows(group_qids: dict[tuple[str, str, str, str], set[str]]) -> list[dict[str, str]]: + by_model_topic_country: dict[tuple[str, str, str], dict[str, set[str]]] = defaultdict(dict) + for (model, topic, language, country), qids in group_qids.items(): + by_model_topic_country[(model, topic, country)][language] = qids + + consensus_by_model: dict[str, list[float]] = defaultdict(list) + for (model, _topic, _country), lang_qids in by_model_topic_country.items(): + languages = sorted(lang_qids) + for idx, lang_a in enumerate(languages): + for lang_b in languages[idx + 1 :]: + consensus_by_model[model].append( + culture_consensus(lang_qids[lang_a], lang_qids[lang_b]) + ) + + rows = [] + for model, values in sorted(consensus_by_model.items()): + if not values: + continue + value = mean(values) + status = "PASS" if 0.08 <= value <= 0.25 else "MISMATCH" + rows.append( + { + "metric": "Average consensus", + "slice": model, + "computed": f"{value:.3f}", + "paper_reference": REFERENCE_NOTES["consensus"], + "status": status, + "comment": "Band check only; exact values may differ with released linking.", + } + ) + return rows + + +def _markdown_table(rows: list[dict[str, str]]) -> str: + headers = ["metric", "slice", "computed", "paper_reference", "status", "comment"] + lines = [ + "| " + " | ".join(headers) + " |", + "| " + " | ".join(["---"] * len(headers)) + " |", + ] + for row in rows: + values = [row.get(header, "").replace("|", "\\|") for header in headers] + lines.append("| " + " | ".join(values) + " |") + return "\n".join(lines) + + +def _write_report(rows: list[dict[str, str]], output_path: Path, payload: dict[str, Any]) -> None: + output_path.parent.mkdir(parents=True, exist_ok=True) + body = [ + "# MAKIEval Fidelity Report", + "", + "Generated by `code/validate_fidelity.py`.", + "", + f"- Full dataset mode: `{payload.get('full')}`", + f"- Rows scanned: `{payload.get('rows_seen')}`", + f"- Group count with non-null metric QIDs: `{len(payload.get('group_qids', {}))}`", + "", + _markdown_table(rows), + "", + ] + output_path.write_text("\n".join(body), encoding="utf-8") + + +def main() -> None: + parser = argparse.ArgumentParser(description="Validate MAKIEval metric fidelity checks.") + parser.add_argument( + "--full", + action="store_true", + help="Scan the full Hugging Face parquet release.", + ) + parser.add_argument( + "--limit", + type=int, + default=100000, + help="Development limit used only without --full.", + ) + parser.add_argument( + "--cache-dir", + default="results/fidelity", + help="Directory for slice/group-QID cache files.", + ) + parser.add_argument( + "--force-refresh", + action="store_true", + help="Ignore existing cache and rescan parquet shards.", + ) + parser.add_argument( + "--output", + default="docs/FIDELITY_REPORT.md", + help="Markdown report path.", + ) + args = parser.parse_args() + + if not args.full: + print( + "WARNING: validate_fidelity.py without --full is a development smoke test; " + "paper-facing checks require --full.", + file=sys.stderr, + ) + + payload = _load_or_compute_cache(args) + group_qids = _deserialize_group_qids(payload.get("group_qids", {})) + rows = [_deepseek_us_book_row(payload, group_qids)] + rows.extend(_average_diversity_rows(group_qids)) + rows.extend(_average_consensus_rows(group_qids)) + + output_path = Path(args.output) + _write_report(rows, output_path, payload) + + statuses = Counter(row["status"] for row in rows) + print(f"Wrote fidelity report to {output_path}") + print( + "Summary: " + + ", ".join(f"{status}={count}" for status, count in sorted(statuses.items())) + ) + + +if __name__ == "__main__": + main() diff --git a/docs/DEVIATIONS.md b/docs/DEVIATIONS.md new file mode 100644 index 0000000..b019dbf --- /dev/null +++ b/docs/DEVIATIONS.md @@ -0,0 +1,50 @@ +# Deviations from upstream / paper + +This fork documents reproducibility additions for the public +`mainlp/MAKIEval` release and notes where the released Hugging Face data cannot +be compared directly to paper-level numbers. + +## Metrics layer + +| Item | Status | Notes | +|------|--------|-------| +| Metric computation | **Added** | Upstream did not include a runnable implementation for granularity, diversity, culture specificity, or culture consensus. This branch adds `code/metrics.py`, `code/data_loading.py`, and `code/run_metrics.py`. | +| Fidelity validation | **Added** | `code/validate_fidelity.py` scans Hugging Face parquet shards and writes `docs/FIDELITY_REPORT.md` with paper-facing sanity checks. MISMATCH rows are reported rather than hidden because released linking can differ from paper-side attribution. | + +## Repository hygiene + +| Item | Status | Notes | +|------|--------|-------| +| API keys | **Changed** | Hardcoded placeholder/default API keys are removed from `code/lm_utils.py`. OpenAI and DeepSeek clients now read `OPENAI_API_KEY` and `DEEPSEEK_API_KEY` from the environment. | +| `entity_extraction.py` syntax | **Fixed** | Restores importability by replacing the invalid `OpenAI(api_key )=` line with an environment-backed client initialization. | + +## Metric specifics + +- **Null QIDs:** Excluded from diversity, consensus QID sets, and + culture-specificity denominators. +- **Culture specificity without lookup:** `run_metrics.py` reports `0.0` + unless a `--country-lookup` JSON is supplied; the Hugging Face rows do not + include origin-country metadata per entity. +- **Excluded entity types:** `place`, `person_name`, `listener_name`, + `reader_name`. +- **Metric CLI default:** `run_metrics.py` defaults to a deterministic + `head(5000)` limit for quick smoke tests. This is biased toward early Hugging + Face rows and is **not comparable to paper diversity/consensus values**; pass + `--full` for all matching rows. Partial outputs include `is_partial_sample`, + `n_rows_used`, and warning metadata. +- **Partial group completeness:** `run_metrics.py` scans filtered parquet group + columns to compare actually present Hugging Face + `(model, topic, language, country_region)` groups against computed groups and + warns when partial sampling drops complete groups. +- **Figure 2 ZH/consensus note:** The EN and DE Figure 2 book columns are + reproducible from stated QIDs and origin attributions in + `tests/test_metrics.py`. The ZH column / consensus=0.5 value could not be + reproduced from the stated EN/DE entity attributions alone; the corresponding + test is marked `xfail` rather than inventing an expected value. + +## README corrections + +- Dataset lists **7 LLMs** (not 13). +- Hugging Face release has **approximately 5.25M rows**; the paper mentions + 85.8M generated texts. At the response level, the expected order of magnitude + is about 6M rows (1,716 prompts x 500 responses x 7 models). diff --git a/docs/FIDELITY_REPORT.md b/docs/FIDELITY_REPORT.md new file mode 100644 index 0000000..0d3c6b0 --- /dev/null +++ b/docs/FIDELITY_REPORT.md @@ -0,0 +1,25 @@ +# MAKIEval Fidelity Report + +Generated by `code/validate_fidelity.py`. + +- Full dataset mode: `True` +- Rows scanned: `5247018` +- Group count with non-null metric QIDs: `10740` + +| metric | slice | computed | paper_reference | status | comment | +| --- | --- | --- | --- | --- | --- | +| DeepSeek US book dominance | DeepSeek-V3 / book / en / US | diversity=6; dominant=Q212340 373/374 (99.7%) | Paper Section 6.2: DeepSeek-V3, English, book, US is dominated by To Kill a Mockingbird (~499/500), diversity approx 1. | MISMATCH | PASS threshold: diversity <= 2 and dominant QID row frequency >= 90%. | +| Average diversity | Aya-Expanse-8B | 49.74 | Paper Table 12 order-of-magnitude: Llama-3.1-8B highest (~36-39), DeepSeek lowest (~14-17). | PASS | Broad order-of-magnitude check; no exact per-model target asserted. | +| Average diversity | ChatGPT-4o-mini | 45.59 | Paper Table 12 order-of-magnitude: Llama-3.1-8B highest (~36-39), DeepSeek lowest (~14-17). | PASS | Broad order-of-magnitude check; no exact per-model target asserted. | +| Average diversity | DeepSeek-V3 | 31.13 | Paper Table 12 order-of-magnitude: Llama-3.1-8B highest (~36-39), DeepSeek lowest (~14-17). | MISMATCH | Lowest model observed: DeepSeek-V3. | +| Average diversity | Llama-3.1-8B-Instruct | 93.15 | Paper Table 12 order-of-magnitude: Llama-3.1-8B highest (~36-39), DeepSeek lowest (~14-17). | MISMATCH | Highest model observed: Llama-3.1-8B-Instruct. | +| Average diversity | Llama-3.3-70B-Instruct | 52.53 | Paper Table 12 order-of-magnitude: Llama-3.1-8B highest (~36-39), DeepSeek lowest (~14-17). | PASS | Broad order-of-magnitude check; no exact per-model target asserted. | +| Average diversity | Mistral-7B-Instruct-v0.1 | 76.82 | Paper Table 12 order-of-magnitude: Llama-3.1-8B highest (~36-39), DeepSeek lowest (~14-17). | MISMATCH | Broad order-of-magnitude check; no exact per-model target asserted. | +| Average diversity | Qwen2.5-7B-Instruct | 52.08 | Paper Table 12 order-of-magnitude: Llama-3.1-8B highest (~36-39), DeepSeek lowest (~14-17). | PASS | Broad order-of-magnitude check; no exact per-model target asserted. | +| Average consensus | Aya-Expanse-8B | 0.085 | Paper Table 3 order-of-magnitude: model averages roughly 0.11-0.21; larger models around 0.20. | PASS | Band check only; exact values may differ with released linking. | +| Average consensus | ChatGPT-4o-mini | 0.101 | Paper Table 3 order-of-magnitude: model averages roughly 0.11-0.21; larger models around 0.20. | PASS | Band check only; exact values may differ with released linking. | +| Average consensus | DeepSeek-V3 | 0.102 | Paper Table 3 order-of-magnitude: model averages roughly 0.11-0.21; larger models around 0.20. | PASS | Band check only; exact values may differ with released linking. | +| Average consensus | Llama-3.1-8B-Instruct | 0.077 | Paper Table 3 order-of-magnitude: model averages roughly 0.11-0.21; larger models around 0.20. | MISMATCH | Band check only; exact values may differ with released linking. | +| Average consensus | Llama-3.3-70B-Instruct | 0.101 | Paper Table 3 order-of-magnitude: model averages roughly 0.11-0.21; larger models around 0.20. | PASS | Band check only; exact values may differ with released linking. | +| Average consensus | Mistral-7B-Instruct-v0.1 | 0.036 | Paper Table 3 order-of-magnitude: model averages roughly 0.11-0.21; larger models around 0.20. | MISMATCH | Band check only; exact values may differ with released linking. | +| Average consensus | Qwen2.5-7B-Instruct | 0.075 | Paper Table 3 order-of-magnitude: model averages roughly 0.11-0.21; larger models around 0.20. | MISMATCH | Band check only; exact values may differ with released linking. | diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bb07ad4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +datasets>=2.19.0,<4.0.0 +pandas>=2.0.0,<3.0.0 +pyarrow>=15.0.0,<25.0.0 +SPARQLWrapper>=2.0.0,<3.0.0 +openai>=1.30.0,<3.0.0 +torch>=2.1.0,<3.0.0 +transformers>=4.40.0,<5.0.0 +tqdm>=4.66.0,<5.0.0 +pytest>=8.0.0,<10.0.0 +numpy>=1.26.0,<3.0.0 +wikipedia>=1.4.0,<2.0.0 +requests>=2.32.0,<3.0.0 diff --git a/tests/test_data_loading.py b/tests/test_data_loading.py new file mode 100644 index 0000000..9a0dac2 --- /dev/null +++ b/tests/test_data_loading.py @@ -0,0 +1,68 @@ +import os +import sys + +import pandas as pd + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "code")) + +import data_loading + + +def test_load_expected_group_keys_exact_uses_present_filtered_groups(monkeypatch): + frames = [ + pd.DataFrame( + [ + { + "model": "Model-A", + "topic": "book", + "language": "en", + "country_region": "united states", + }, + { + "model": "Model-A", + "topic": "book", + "language": "en", + "country_region": "united states", + }, + { + "model": "Model-A", + "topic": "book", + "language": "de", + "country_region": "germany", + }, + ] + ), + pd.DataFrame( + [ + { + "model": "Model-A", + "topic": "book", + "language": "zh", + "country_region": "china", + } + ] + ), + ] + + def fake_iter_makieval_parquet_frames(split, filters, columns): + assert split == "train" + assert filters == {"model": "Model-A", "topic": "book"} + assert columns == data_loading.GROUP_COLUMNS + yield from frames + + monkeypatch.setattr( + data_loading, + "iter_makieval_parquet_frames", + fake_iter_makieval_parquet_frames, + ) + + groups = data_loading.load_expected_group_keys( + filters={"model": "Model-A", "topic": "books"}, + exact=True, + ) + + assert groups == [ + ("Model-A", "book", "de", "germany"), + ("Model-A", "book", "en", "united states"), + ("Model-A", "book", "zh", "china"), + ] diff --git a/tests/test_metrics.py b/tests/test_metrics.py new file mode 100644 index 0000000..b246718 --- /dev/null +++ b/tests/test_metrics.py @@ -0,0 +1,181 @@ +"""Unit tests for MAKIEval metrics (paper Figure 2 / Section 4).""" + +import os +import sys + +import pytest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "code")) + +from data_loading import filter_entities_for_metrics +from metrics import ( + culture_consensus, + culture_specificity, + diversity, + granularity, +) + + +# Figure 2 US-books example: three extracted entities with one repeated title. +FIGURE2_ENTITIES = [ + {"entity": "To Kill a Mockingbird", "entity_type": "book_title", "qid": "Q1114275"}, + {"entity": "To Kill a Mockingbird", "entity_type": "book_title", "qid": "Q1114275"}, + {"entity": "American literature", "entity_type": "book_genre", "qid": "Q36279"}, +] + + +def test_diversity_figure2_us_books(): + assert diversity(FIGURE2_ENTITIES) == 2 + + +def test_granularity_figure2_us_books(): + # Two specific book titles + one genre category -> (1+1+0)/3 + assert granularity(FIGURE2_ENTITIES) == pytest.approx(2 / 3) + + +def test_culture_specificity_us_match(): + lookup = { + "Q1114275": "United States", + "Q36279": "United States", + } + score = culture_specificity(FIGURE2_ENTITIES, "United States", lookup) + assert score == pytest.approx(1.0) + + +def test_culture_specificity_partial_match(): + entities = [ + {"entity": "To Kill a Mockingbird", "entity_type": "book_title", "qid": "Q1114275"}, + {"entity": "American literature", "entity_type": "book_genre", "qid": "Q36279"}, + ] + lookup = { + "Q1114275": "United States", + "Q36279": "United Kingdom", + } + score = culture_specificity(entities, "United States", lookup) + assert score == pytest.approx(0.5) + + +def test_culture_consensus_jaccard(): + qids_a = {"Q1", "Q2", "Q3"} + qids_b = {"Q2", "Q3", "Q4"} + assert culture_consensus(qids_a, qids_b) == pytest.approx(2 / 4) + + +def test_culture_consensus_empty_union(): + assert culture_consensus([], []) == 0.0 + + +def test_diversity_excludes_null_qids(): + entities = [ + {"entity": "tea", "entity_type": "drink_name", "qid": None}, + {"entity": "coffee", "entity_type": "drink_name", "qid": "Q8486"}, + ] + assert diversity(entities) == 1 + + +def test_granularity_category_only(): + entities = [ + {"entity": "vegetables", "entity_type": "ingredient_category", "qid": "Q11004"}, + {"entity": "stir-fry", "entity_type": "dish_category", "qid": "Q381160"}, + ] + assert granularity(entities) == 0.0 + + +def test_jaccard_exact_paper_formula_cases(): + assert culture_consensus({"Q1", "Q2"}, {"Q2", "Q3"}) == pytest.approx(1 / 3) + assert culture_consensus({"Q1", "Q2"}, {"Q1", "Q2"}) == pytest.approx(1.0) + assert culture_consensus({"Q1"}, {"Q2"}) == pytest.approx(0.0) + # Empty-union policy: Jaccard is defined as 0.0 for two empty QID sets. + assert culture_consensus(set(), set()) == pytest.approx(0.0) + + +def test_diversity_counts_unique_qids_and_excludes_nulls(): + entities = [ + {"entity": "a", "entity_type": "drink_name", "qid": "Q1"}, + {"entity": "a again", "entity_type": "drink_name", "qid": "Q1"}, + {"entity": "b", "entity_type": "drink_name", "qid": "Q2"}, + {"entity": "missing", "entity_type": "drink_name", "qid": None}, + {"entity": "na", "entity_type": "drink_name", "qid": "NA"}, + ] + assert diversity(entities) == 2 + + +def test_granularity_specific_category_and_mixed_eq1(): + specific = [ + {"entity": "ramen", "entity_type": "dish", "qid": "Q1"}, + {"entity": "tea", "entity_type": "drink_name", "qid": "Q2"}, + ] + categories = [ + {"entity": "soups", "entity_type": "dish_category", "qid": "Q3"}, + {"entity": "genres", "entity_type": "book_genre", "qid": "Q4"}, + ] + assert granularity(specific) == pytest.approx(1.0) + assert granularity(categories) == pytest.approx(0.0) + assert granularity(specific + categories) == pytest.approx(0.5) + + +def test_culture_specificity_known_origin_ratio(): + entities = [ + {"entity": "us item", "entity_type": "book_title", "qid": "Q1"}, + {"entity": "uk item", "entity_type": "book_title", "qid": "Q2"}, + {"entity": "missing origin", "entity_type": "book_title", "qid": "Q3"}, + ] + lookup = {"Q1": "United States", "Q2": "United Kingdom"} + assert culture_specificity(entities, "US", lookup) == pytest.approx(1 / 2) + + +def test_figure2_en_books_reproduction(): + entities = [ + {"entity": "The Great Gatsby", "entity_type": "book_title", "qid": "Q214371"}, + {"entity": "To Kill a Mockingbird", "entity_type": "book_title", "qid": "Q212340"}, + {"entity": "To Kill a Mockingbird", "entity_type": "book_title", "qid": "Q212340"}, + ] + lookup = {"Q214371": "United States", "Q212340": "United States"} + assert diversity(entities) == 2 + assert granularity(entities) == pytest.approx(1.0) + assert culture_specificity(entities, "US", lookup) == pytest.approx(1.0) + + +def test_figure2_de_books_reproduction(): + entities = [ + {"entity": "Wer die Nachtigall stört", "entity_type": "book_title", "qid": "Q212340"}, + {"entity": "Der alte Mann und das Meer", "entity_type": "book_title", "qid": "Q26505"}, + {"entity": "Der Hobbit", "entity_type": "book_title", "qid": "Q74287"}, + ] + lookup = { + "Q212340": "United States", + "Q26505": "United States", + "Q74287": "United Kingdom", + } + assert diversity(entities) == 3 + assert granularity(entities) == pytest.approx(1.0) + assert culture_specificity(entities, "United States", lookup) == pytest.approx(2 / 3) + + +@pytest.mark.xfail( + reason=( + "Figure 2 ZH-column / consensus=0.5 cannot be derived from the stated " + "EN/DE entity attributions here; EN-DE Jaccard from stated QIDs is 0.25." + ) +) +def test_figure2_consensus_reference_not_directly_reproducible(): + en_qids = {"Q214371", "Q212340"} + de_qids = {"Q212340", "Q26505", "Q74287"} + assert culture_consensus(en_qids, de_qids) == pytest.approx(0.5) + + +def test_metric_entity_filter_excludes_non_cultural_entity_types(): + entities = [ + {"entity": "Lagos", "entity_type": "place", "qid": None}, + {"entity": "Ali", "entity_type": "person_name", "qid": None}, + {"entity": "listener", "entity_type": "listener_name", "qid": None}, + {"entity": "reader", "entity_type": "reader_name", "qid": None}, + {"entity": "tea", "entity_type": "drink_name", "qid": "Q6097"}, + ] + kept, excluded = filter_entities_for_metrics(entities) + assert excluded == 4 + assert kept == [{"entity": "tea", "entity_type": "drink_name", "qid": "Q6097"}] + + +def test_consensus_excludes_null_qids(): + assert culture_consensus(["Q1", None, ""], ["Q1", "Q2", None]) == pytest.approx(1 / 2) From 5443ca2b970dd1bfb7270ae93aac62214f2cf103 Mon Sep 17 00:00:00 2001 From: ErenAta16 Date: Tue, 23 Jun 2026 19:51:31 +0300 Subject: [PATCH 04/14] docs: use neutral fidelity report wording --- code/validate_fidelity.py | 2 +- docs/FIDELITY_REPORT.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/validate_fidelity.py b/code/validate_fidelity.py index de71662..6fd3df7 100644 --- a/code/validate_fidelity.py +++ b/code/validate_fidelity.py @@ -240,7 +240,7 @@ def _write_report(rows: list[dict[str, str]], output_path: Path, payload: dict[s body = [ "# MAKIEval Fidelity Report", "", - "Generated by `code/validate_fidelity.py`.", + "Source: `code/validate_fidelity.py`.", "", f"- Full dataset mode: `{payload.get('full')}`", f"- Rows scanned: `{payload.get('rows_seen')}`", diff --git a/docs/FIDELITY_REPORT.md b/docs/FIDELITY_REPORT.md index 0d3c6b0..217fb27 100644 --- a/docs/FIDELITY_REPORT.md +++ b/docs/FIDELITY_REPORT.md @@ -1,6 +1,6 @@ # MAKIEval Fidelity Report -Generated by `code/validate_fidelity.py`. +Source: `code/validate_fidelity.py`. - Full dataset mode: `True` - Rows scanned: `5247018` From 412165273ae46f3091b4d83ae9da0abbe03b4036 Mon Sep 17 00:00:00 2001 From: ErenAta16 Date: Wed, 24 Jun 2026 00:18:47 +0300 Subject: [PATCH 05/14] fix: restore runnable experiment imports --- code/run_experiment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/run_experiment.py b/code/run_experiment.py index 2908ae0..edbbcfa 100644 --- a/code/run_experiment.py +++ b/code/run_experiment.py @@ -3,7 +3,7 @@ import argparse import os import logging -from entity_extraction_nobatch import get_key_words +from entity_extraction import get_key_words from analysis_food import fetch_wikidata_food_info from analysis_beverage import fetch_wikidata_beverage_info from analysis_clothing import fetch_wikidata_clothing_info @@ -17,7 +17,7 @@ import json import shlex from collections import defaultdict, Counter -sys.path.append(PATH) +sys.path.append(os.path.dirname(os.path.abspath(__file__))) import prompt_construct import lm_utils import re @@ -415,4 +415,4 @@ def filter_nan_qid(group): } with open(runtime_log_path, "w", encoding="utf-8") as f: json.dump(runtime_data, f, indent=4, ensure_ascii=False) - logging.info(f"Execution time log saved to {runtime_log_path}") \ No newline at end of file + logging.info(f"Execution time log saved to {runtime_log_path}") From 056ca962d3531c29b2db74271ef307c0a1663a98 Mon Sep 17 00:00:00 2001 From: ErenAta16 Date: Wed, 24 Jun 2026 00:18:54 +0300 Subject: [PATCH 06/14] fix: restore shared Wikidata analysis helper --- code/analysis.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 code/analysis.py diff --git a/code/analysis.py b/code/analysis.py new file mode 100644 index 0000000..d22014b --- /dev/null +++ b/code/analysis.py @@ -0,0 +1,96 @@ +# RECONSTRUCTED from paper Section 3.3 & 4.3; original analysis.py was not released in upstream repo. + +import re +import time +from functools import lru_cache + +import pandas as pd +from SPARQLWrapper import SPARQLWrapper, JSON + +sparql = SPARQLWrapper("https://query.wikidata.org/sparql") +sparql.addCustomHttpHeader("User-Agent", "MAKIEval/1.0 (cultural-awareness-evaluation)") + +COUNTRY_SOURCE_COLUMNS = ( + "Author Countries", + "Performer Countries", + "Origin Countries", +) + + +@lru_cache(maxsize=4096) +def _query_wikidata_origin_country(qid: str) -> str: + """Resolve origin country for a QID (P495 preferred, then P17).""" + if not qid or qid == "NA": + return "NA" + + query = f""" + SELECT ?countryLabel WHERE {{ + OPTIONAL {{ wd:{qid} wdt:P495 ?country. }} + OPTIONAL {{ wd:{qid} wdt:P17 ?country. }} + ?country rdfs:label ?countryLabel. + FILTER(LANG(?countryLabel) = "en") + }} + LIMIT 1 + """ + for attempt in range(3): + try: + sparql.setQuery(query) + sparql.setReturnFormat(JSON) + results = sparql.query().convert() + bindings = results["results"]["bindings"] + if bindings and "countryLabel" in bindings[0]: + return bindings[0]["countryLabel"]["value"] + return "NA" + except Exception: + time.sleep(1 + attempt) + return "NA" + + +def _parse_countries_from_information(information: str) -> str: + """Best-effort parse of country names embedded in the Information field.""" + if not information or information == "NA": + return "NA" + + # Wikidata info strings often end with comma-separated English country labels. + parts = [part.strip() for part in information.split(",") if part.strip()] + if not parts: + return "NA" + + # Heuristic: trailing tokens that look like country names (Title Case words). + country_like = [] + for part in reversed(parts): + if re.match(r"^[A-Z][a-zA-Z\s\-']+$", part): + country_like.insert(0, part) + else: + break + + return ", ".join(country_like) if country_like else "NA" + + +def _row_origin_country(row: pd.Series) -> str: + for column in COUNTRY_SOURCE_COLUMNS: + if column in row.index and row[column] not in (None, "", "NA"): + return str(row[column]) + + information = row.get("Information", "NA") + parsed = _parse_countries_from_information(str(information)) + if parsed != "NA": + return parsed + + qid = row.get("Q_ID", "NA") + return _query_wikidata_origin_country(str(qid)) + + +def process_wikidata_country_info(df: pd.DataFrame) -> pd.DataFrame: + """ + Enrich Wikidata entity rows with origin-country metadata for culture specificity. + + Expected input columns include: + Q_ID, Original Label, Label, Information, is_category, Alternative QIDs + """ + if df.empty: + return df + + result = df.copy() + result["Origin Country"] = result.apply(_row_origin_country, axis=1) + return result From dae7bec6ad0692bf6dbaabfc8ccdaa20d52e7a49 Mon Sep 17 00:00:00 2001 From: ErenAta16 Date: Wed, 24 Jun 2026 01:21:13 +0300 Subject: [PATCH 07/14] feat: add published data quality audit --- code/quality_report.py | 509 ++++++++++++++++++++++++++++++++++++ docs/DATA_QUALITY_REPORT.md | 120 +++++++++ requirements.txt | 1 + 3 files changed, 630 insertions(+) create mode 100644 code/quality_report.py create mode 100644 docs/DATA_QUALITY_REPORT.md diff --git a/code/quality_report.py b/code/quality_report.py new file mode 100644 index 0000000..f1560e6 --- /dev/null +++ b/code/quality_report.py @@ -0,0 +1,509 @@ +"""Quality checks for the published Raoyuan/MAKIEval Hugging Face dataset.""" + +from __future__ import annotations + +import argparse +import json +import os +import random +import re +import sys +import unicodedata +from collections import Counter, defaultdict +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +import pandas as pd +from tqdm import tqdm + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from data_loading import GROUP_COLUMNS, DEFAULT_COLUMNS, iter_makieval_parquet_frames, parse_entities_field + +try: + import langid +except Exception: # pragma: no cover - exercised only when optional dep is absent. + langid = None + + +REPORT_NOTE = ( + "Scope: published HF dataset, version may differ from paper (per author)." +) + +LANG_ALIASES = { + "zh-tw": {"zh", "zh-tw"}, + "zh": {"zh", "zh-tw"}, +} + +SUSPICIOUS_LABELS = { + "cocaine", + "coca\u00edna", + "kokain", + "caffeine", + "kafein", + "curiosity", + "merak", +} + +SUSPICIOUS_QIDS = { + "Q422", + "Q60235", +} + +SUSPICIOUS_TYPES = { + "dish_name", + "dish", + "specific_ingredient", + "drink_name", + "beverage_category", +} + + +def normalize_label(label: str) -> str: + """Normalize entity surface forms for rough cross-row comparisons.""" + text = unicodedata.normalize("NFKD", str(label).strip().lower()) + text = "".join(ch for ch in text if not unicodedata.combining(ch)) + text = re.sub(r"^[\"'`]+|[\"'`]+$", "", text) + text = re.sub(r"^(the|a|an)\s+", "", text) + text = re.sub(r"^\u0627\u0644", "", text) + text = re.sub(r"\s+", " ", text) + return text + + +def entity_label(entity: dict[str, Any]) -> str: + for key in ("entity", "label", "text", "name"): + value = entity.get(key) + if value: + return str(value) + return "" + + +def entity_qid(entity: dict[str, Any]) -> str | None: + qid = entity.get("qid") + if qid is None or qid == "" or str(qid).upper() == "NA": + return None + return str(qid) + + +def is_empty_text(text: Any) -> bool: + return not str(text or "").strip() + + +def has_repeated_ngram_block(text: str, threshold: int = 10) -> bool: + if re.search(r"(.)\1{30,}", text): + return True + tokens = re.findall(r"\w+|[^\w\s]", text.lower(), flags=re.UNICODE) + if len(tokens) < threshold * 2: + return False + for n in (1, 2, 3): + previous = None + run = 0 + for idx in range(0, len(tokens) - n + 1, n): + chunk = tuple(tokens[idx : idx + n]) + if chunk == previous: + run += 1 + else: + previous = chunk + run = 1 + if run >= threshold: + return True + return False + + +def has_repeated_sentence(text: str, threshold: int = 3) -> bool: + sentences = [ + re.sub(r"\s+", " ", part.strip().lower()) + for part in re.split(r"[.!?\n\u3002\uff01\uff1f]+", text) + if part.strip() + ] + if not sentences: + return False + counts = Counter(sentences) + return any(count >= threshold for count in counts.values()) + + +def detect_language(text: str) -> str: + if is_empty_text(text): + return "und" + if langid is not None: + try: + return langid.classify(text)[0].lower() + except Exception: + return "und" + + # Fallback keeps the report runnable when langid is not installed. + if re.search(r"[\u0600-\u06ff]", text): + return "ar" + if re.search(r"[\u3040-\u30ff]", text): + return "ja" + if re.search(r"[\uac00-\ud7af]", text): + return "ko" + if re.search(r"[\u4e00-\u9fff]", text): + return "zh" + if re.search(r"[\u0e00-\u0e7f]", text): + return "th" + if re.search(r"[\u0900-\u097f]", text): + return "hi" + return "unknown" + + +def language_matches(expected: str, detected: str) -> bool: + expected_norm = str(expected).lower() + detected_norm = str(detected).lower() + if expected_norm == detected_norm: + return True + return detected_norm in LANG_ALIASES.get(expected_norm, set()) + + +def short_text(text: Any, limit: int = 180) -> str: + value = re.sub(r"\s+", " ", str(text or "").strip()) + if len(value) <= limit: + return value + return value[: limit - 3] + "..." + + +@dataclass +class BucketStats: + rows: int = 0 + repeated_ngram_rows: int = 0 + repeated_sentence_rows: int = 0 + empty_entity_rows: int = 0 + language_checked_rows: int = 0 + language_mismatch_rows: int = 0 + entity_count: int = 0 + missing_qid_count: int = 0 + suspicious_count: int = 0 + + +@dataclass +class QualityState: + overall: BucketStats = field(default_factory=BucketStats) + by_slice: dict[tuple[str, str, str], BucketStats] = field( + default_factory=lambda: defaultdict(BucketStats) + ) + by_topic: dict[str, BucketStats] = field(default_factory=lambda: defaultdict(BucketStats)) + examples: dict[str, list[dict[str, Any]]] = field( + default_factory=lambda: defaultdict(list) + ) + label_qids: dict[str, Counter] = field(default_factory=lambda: defaultdict(Counter)) + observed_groups: set[tuple[str, str, str, str]] = field(default_factory=set) + + +def add_example(state: QualityState, kind: str, row: dict[str, Any], extra: str = "") -> None: + if len(state.examples[kind]) >= 3: + return + state.examples[kind].append( + { + "model": row.get("model"), + "topic": row.get("topic"), + "language": row.get("language"), + "country_region": row.get("country_region"), + "detail": extra, + "generated_text": short_text(row.get("generated_text")), + } + ) + + +def update_bucket(bucket: BucketStats, row_flags: dict[str, bool], entities: list[dict[str, Any]]) -> None: + bucket.rows += 1 + bucket.repeated_ngram_rows += int(row_flags["repeated_ngram"]) + bucket.repeated_sentence_rows += int(row_flags["repeated_sentence"]) + bucket.empty_entity_rows += int(row_flags["empty_entities"]) + bucket.language_checked_rows += int(row_flags["language_checked"]) + bucket.language_mismatch_rows += int(row_flags["language_mismatch"]) + bucket.suspicious_count += int(row_flags["suspicious"]) + bucket.entity_count += len(entities) + bucket.missing_qid_count += sum(1 for entity in entities if not entity_qid(entity)) + + +def process_row(state: QualityState, row: dict[str, Any]) -> None: + key = ( + str(row.get("model")), + str(row.get("topic")), + str(row.get("language")), + str(row.get("country_region")), + ) + state.observed_groups.add(key) + + entities, ok = parse_entities_field(row.get("entities")) + if not ok: + entities = [] + + text = str(row.get("generated_text") or "") + detected_language = detect_language(text) + language_checked = detected_language not in {"und", "unknown"} + language_mismatch = language_checked and not language_matches( + str(row.get("language")), detected_language + ) + + suspicious_entities = [] + for entity in entities: + label = normalize_label(entity_label(entity)) + qid = entity_qid(entity) + entity_type = str(entity.get("entity_type", "")).lower() + if label and qid: + state.label_qids[label][qid] += 1 + if entity_type in SUSPICIOUS_TYPES and ( + label in SUSPICIOUS_LABELS or qid in SUSPICIOUS_QIDS + ): + suspicious_entities.append(entity) + + row_flags = { + "repeated_ngram": has_repeated_ngram_block(text), + "repeated_sentence": has_repeated_sentence(text), + "empty_entities": len(entities) == 0, + "language_checked": language_checked, + "language_mismatch": language_mismatch, + "suspicious": bool(suspicious_entities), + } + + update_bucket(state.overall, row_flags, entities) + update_bucket( + state.by_slice[(str(row.get("model")), str(row.get("language")), str(row.get("topic")))], + row_flags, + entities, + ) + update_bucket(state.by_topic[str(row.get("topic"))], row_flags, entities) + + if row_flags["repeated_ngram"]: + add_example(state, "repeated_ngram", row) + if row_flags["repeated_sentence"]: + add_example(state, "repeated_sentence", row) + if row_flags["empty_entities"]: + add_example(state, "empty_entities", row) + if row_flags["language_mismatch"]: + add_example(state, "language_mismatch", row, f"detected={detected_language}") + if suspicious_entities: + detail = ", ".join( + f"{entity_label(entity)} ({entity.get('entity_type')}, {entity_qid(entity) or 'NA'})" + for entity in suspicious_entities[:3] + ) + add_example(state, "suspicious_entities", row, detail) + + +def scan_rows(sample: int | None, seed: int, full: bool) -> QualityState: + rng = random.Random(seed) + state = QualityState() + reservoirs: dict[tuple[str, str, str], list[dict[str, Any]]] = defaultdict(list) + seen_by_sample_key: Counter = Counter() + columns = DEFAULT_COLUMNS + + for shard in tqdm( + iter_makieval_parquet_frames(columns=columns), + desc="Reading MAKIEval parquet shards", + ): + if shard.empty: + continue + for record in shard.to_dict(orient="records"): + group_key = ( + str(record.get("model")), + str(record.get("topic")), + str(record.get("language")), + str(record.get("country_region")), + ) + state.observed_groups.add(group_key) + + if full or sample is None: + process_row(state, record) + continue + + sample_key = ( + str(record.get("model")), + str(record.get("language")), + str(record.get("topic")), + ) + seen_by_sample_key[sample_key] += 1 + seen = seen_by_sample_key[sample_key] + bucket = reservoirs[sample_key] + if len(bucket) < sample: + bucket.append(record) + else: + replace_at = rng.randrange(seen) + if replace_at < sample: + bucket[replace_at] = record + + if not full and sample is not None: + for bucket in reservoirs.values(): + for record in bucket: + process_row(state, record) + + return state + + +def pct(numerator: int, denominator: int) -> str: + if denominator == 0: + return "0.00%" + return f"{100 * numerator / denominator:.2f}%" + + +def bucket_row(label: str, stats: BucketStats) -> str: + qid_rate = pct(stats.missing_qid_count, stats.entity_count) + lang_rate = pct(stats.language_mismatch_rows, stats.language_checked_rows) + return ( + f"| {label} | {stats.rows} | {pct(stats.repeated_ngram_rows, stats.rows)} | " + f"{pct(stats.repeated_sentence_rows, stats.rows)} | " + f"{pct(stats.empty_entity_rows, stats.rows)} | {lang_rate} | {qid_rate} | " + f"{stats.suspicious_count} |" + ) + + +def top_surface_inconsistencies(state: QualityState, limit: int = 20) -> list[tuple[str, Counter]]: + rows = [ + (label, counts) + for label, counts in state.label_qids.items() + if len(counts) > 1 + ] + rows.sort(key=lambda item: (len(item[1]), sum(item[1].values())), reverse=True) + return rows[:limit] + + +def examples_markdown(state: QualityState, kind: str) -> list[str]: + rows = state.examples.get(kind, []) + if not rows: + return ["No examples captured in this run."] + lines = [] + for row in rows: + context = ( + f"{row['model']} / {row['topic']} / {row['language']} / " + f"{row['country_region']}" + ) + detail = f" ({row['detail']})" if row.get("detail") else "" + lines.append(f"- {context}{detail}: {row['generated_text']}") + return lines + + +def build_report(state: QualityState, mode_label: str) -> str: + lines = [ + "# MAKIEval Published Data Quality Report", + "", + REPORT_NOTE, + "", + f"- Mode: `{mode_label}`", + f"- Rows analyzed for quality checks: `{state.overall.rows}`", + f"- Observed `(model, topic, language, country_region)` cells: `{len(state.observed_groups)}`", + "", + "## Summary", + "", + "| Scope | Rows | Repeated n-gram | Repeated sentence | Empty entities | Language mismatch | Missing QID/entity | Suspicious rows |", + "|---|---:|---:|---:|---:|---:|---:|---:|", + bucket_row("Overall", state.overall), + "", + "## Slice Breakdown", + "", + "| Model / Language / Topic | Rows | Repeated n-gram | Repeated sentence | Empty entities | Language mismatch | Missing QID/entity | Suspicious rows |", + "|---|---:|---:|---:|---:|---:|---:|---:|", + ] + + slice_rows = sorted( + state.by_slice.items(), + key=lambda item: ( + item[1].language_mismatch_rows / max(1, item[1].language_checked_rows), + item[1].empty_entity_rows / max(1, item[1].rows), + ), + reverse=True, + ) + for (model, language, topic), stats in slice_rows[:30]: + lines.append(bucket_row(f"{model} / {language} / {topic}", stats)) + + lines.extend( + [ + "", + "## Missing QID By Topic", + "", + "| Topic | Entities | Missing QIDs | Missing rate |", + "|---|---:|---:|---:|", + ] + ) + for topic, stats in sorted(state.by_topic.items()): + lines.append( + f"| {topic} | {stats.entity_count} | {stats.missing_qid_count} | " + f"{pct(stats.missing_qid_count, stats.entity_count)} |" + ) + + lines.extend( + [ + "", + "## Surface Form To QID Inconsistency", + "", + "| Normalized label | Distinct QIDs | Top QIDs |", + "|---|---:|---|", + ] + ) + inconsistencies = top_surface_inconsistencies(state) + if not inconsistencies: + lines.append("| None captured | 0 | |") + for label, counts in inconsistencies: + top_qids = ", ".join(f"{qid} ({count})" for qid, count in counts.most_common(5)) + lines.append(f"| {label} | {len(counts)} | {top_qids} |") + + example_sections = [ + ("Repeated n-gram examples", "repeated_ngram"), + ("Repeated sentence examples", "repeated_sentence"), + ("Empty entity examples", "empty_entities"), + ("Language mismatch examples", "language_mismatch"), + ("Suspicious extraction review candidates", "suspicious_entities"), + ] + for title, key in example_sections: + lines.extend(["", f"## {title}", ""]) + lines.extend(examples_markdown(state, key)) + + lines.extend( + [ + "", + "## Notes", + "", + "- Language detection uses `langid` when installed, with a script-based fallback.", + "- Suspicious extraction rows are rule-based review candidates, not confirmed false positives.", + "- Missing-QID rates are compared descriptively against the paper's 26-35% range; released data may use a different snapshot.", + ] + ) + return "\n".join(lines) + "\n" + + +def main() -> None: + parser = argparse.ArgumentParser(description="Audit published MAKIEval data quality.") + mode = parser.add_mutually_exclusive_group() + mode.add_argument("--sample", type=int, default=200, help="Reservoir sample rows per model-language-topic slice.") + mode.add_argument("--full", action="store_true", help="Analyze every row in the published dataset.") + parser.add_argument("--seed", type=int, default=42, help="Sampling seed") + parser.add_argument( + "--output", + default="docs/DATA_QUALITY_REPORT.md", + help="Markdown report path", + ) + args = parser.parse_args() + + sample = None if args.full else args.sample + state = scan_rows(sample=sample, seed=args.seed, full=args.full) + mode_label = "full" if args.full else f"sample={sample} per model-language-topic" + report = build_report(state, mode_label) + + output = Path(args.output) + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(report, encoding="utf-8") + + print(f"Wrote quality report to {output}") + print( + json.dumps( + { + "mode": mode_label, + "rows_analyzed": state.overall.rows, + "observed_cells": len(state.observed_groups), + "empty_entity_rate": pct(state.overall.empty_entity_rows, state.overall.rows), + "language_mismatch_rate": pct( + state.overall.language_mismatch_rows, + state.overall.language_checked_rows, + ), + "missing_qid_rate": pct( + state.overall.missing_qid_count, + state.overall.entity_count, + ), + }, + indent=2, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/docs/DATA_QUALITY_REPORT.md b/docs/DATA_QUALITY_REPORT.md new file mode 100644 index 0000000..ee5e967 --- /dev/null +++ b/docs/DATA_QUALITY_REPORT.md @@ -0,0 +1,120 @@ +# MAKIEval Published Data Quality Report + +Scope: published HF dataset, version may differ from paper (per author). + +- Mode: `sample=200 per model-language-topic` +- Rows analyzed for quality checks: `109200` +- Observed `(model, topic, language, country_region)` cells: `10740` + +## Summary + +| Scope | Rows | Repeated n-gram | Repeated sentence | Empty entities | Language mismatch | Missing QID/entity | Suspicious rows | +|---|---:|---:|---:|---:|---:|---:|---:| +| Overall | 109200 | 0.11% | 0.10% | 5.16% | 2.76% | 64.23% | 15 | + +## Slice Breakdown + +| Model / Language / Topic | Rows | Repeated n-gram | Repeated sentence | Empty entities | Language mismatch | Missing QID/entity | Suspicious rows | +|---|---:|---:|---:|---:|---:|---:|---:| +| Qwen2.5-7B-Instruct / th / beverage | 200 | 0.00% | 0.00% | 13.50% | 47.50% | 78.67% | 0 | +| Qwen2.5-7B-Instruct / th / music | 200 | 0.00% | 0.00% | 9.50% | 37.50% | 85.42% | 0 | +| Mistral-7B-Instruct-v0.1 / it / beverage | 200 | 0.50% | 0.00% | 6.00% | 36.50% | 60.31% | 0 | +| Qwen2.5-7B-Instruct / ar / music | 200 | 0.00% | 0.00% | 5.00% | 36.50% | 87.22% | 0 | +| Mistral-7B-Instruct-v0.1 / it / food | 200 | 0.50% | 0.50% | 4.00% | 31.00% | 68.19% | 0 | +| Qwen2.5-7B-Instruct / fa / clothing | 200 | 0.00% | 1.50% | 11.50% | 30.50% | 86.51% | 0 | +| Qwen2.5-7B-Instruct / ar / food | 200 | 0.50% | 0.00% | 4.50% | 30.00% | 86.42% | 0 | +| Qwen2.5-7B-Instruct / es / transportation | 200 | 0.00% | 0.00% | 0.00% | 29.00% | 62.05% | 0 | +| Qwen2.5-7B-Instruct / ko / transportation | 200 | 0.00% | 0.00% | 3.50% | 28.00% | 74.71% | 0 | +| Qwen2.5-7B-Instruct / th / transportation | 200 | 0.50% | 0.00% | 14.50% | 27.50% | 54.25% | 0 | +| Qwen2.5-7B-Instruct / th / book | 200 | 0.00% | 0.00% | 7.50% | 27.50% | 88.94% | 0 | +| Mistral-7B-Instruct-v0.1 / de / food | 200 | 0.50% | 0.00% | 0.00% | 27.50% | 54.48% | 0 | +| Qwen2.5-7B-Instruct / en / beverage | 200 | 0.00% | 0.00% | 3.00% | 26.50% | 67.05% | 0 | +| Mistral-7B-Instruct-v0.1 / es / transportation | 200 | 0.50% | 0.50% | 2.00% | 25.50% | 48.54% | 0 | +| Mistral-7B-Instruct-v0.1 / de / book | 200 | 0.00% | 0.00% | 1.00% | 25.50% | 64.71% | 0 | +| Mistral-7B-Instruct-v0.1 / it / clothing | 200 | 0.00% | 1.00% | 2.50% | 25.00% | 69.18% | 0 | +| Qwen2.5-7B-Instruct / it / transportation | 200 | 2.00% | 0.00% | 17.00% | 23.00% | 50.00% | 0 | +| Mistral-7B-Instruct-v0.1 / de / beverage | 200 | 0.00% | 0.50% | 1.50% | 22.50% | 61.76% | 0 | +| Mistral-7B-Instruct-v0.1 / de / music | 200 | 0.00% | 0.00% | 2.00% | 22.00% | 60.47% | 0 | +| Qwen2.5-7B-Instruct / ko / clothing | 200 | 0.00% | 0.00% | 2.00% | 22.00% | 77.82% | 0 | +| Mistral-7B-Instruct-v0.1 / it / book | 200 | 0.00% | 0.00% | 8.50% | 21.50% | 67.23% | 0 | +| Qwen2.5-7B-Instruct / de / clothing | 200 | 0.00% | 0.00% | 7.50% | 19.50% | 56.54% | 0 | +| Mistral-7B-Instruct-v0.1 / es / music | 200 | 0.50% | 0.00% | 30.50% | 19.00% | 73.89% | 0 | +| Qwen2.5-7B-Instruct / ar / transportation | 200 | 0.00% | 0.00% | 11.00% | 19.00% | 76.06% | 0 | +| Qwen2.5-7B-Instruct / th / food | 200 | 0.00% | 0.00% | 2.50% | 18.50% | 88.75% | 0 | +| Mistral-7B-Instruct-v0.1 / th / transportation | 200 | 2.50% | 1.50% | 40.50% | 18.00% | 93.30% | 0 | +| Mistral-7B-Instruct-v0.1 / it / music | 200 | 0.00% | 0.50% | 37.00% | 18.00% | 61.49% | 0 | +| Qwen2.5-7B-Instruct / tr / music | 200 | 2.50% | 0.00% | 3.00% | 18.00% | 47.61% | 0 | +| Mistral-7B-Instruct-v0.1 / es / clothing | 200 | 0.50% | 0.00% | 2.00% | 17.50% | 64.30% | 0 | +| Qwen2.5-7B-Instruct / es / food | 200 | 0.00% | 0.00% | 7.50% | 17.00% | 71.83% | 0 | + +## Missing QID By Topic + +| Topic | Entities | Missing QIDs | Missing rate | +|---|---:|---:|---:| +| beverage | 54389 | 34331 | 63.12% | +| book | 53852 | 38373 | 71.26% | +| clothing | 99994 | 63442 | 63.45% | +| food | 96032 | 59236 | 61.68% | +| music | 49540 | 34847 | 70.34% | +| transportation | 75580 | 45548 | 60.26% | + +## Surface Form To QID Inconsistency + +| Normalized label | Distinct QIDs | Top QIDs | +|---|---:|---| +| 水 | 17 | Q132780 (12), Q54366215 (11), Q87533299 (8), Q4527378 (8), Q27016 (7) | +| drive | 17 | Q5457713 (14), Q1125653 (8), Q15081971 (8), Q5307897 (6), Q3039511 (4) | +| wasser | 15 | Q68834339 (4), Q110508848 (3), Q283 (3), Q21504880 (2), Q769808 (2) | +| bar | 14 | Q187456 (30), Q87533831 (20), Q17015569 (16), Q2138087 (11), Q103510 (8) | +| tango | 13 | Q14390274 (160), Q1980003 (48), Q338450 (18), Q109615012 (12), Q1001165 (3) | +| mate | 13 | Q203540 (100), Q870803 (41), Q122373714 (24), Q16290653 (9), Q20819848 (8) | +| roman | 13 | Q8261 (51), Q2178269 (6), Q2723443 (6), Q2164608 (5), Q484023 (5) | +| عربية | 13 | Q13955 (20), Q20301934 (4), Q1828555 (3), Q131583317 (3), Q28900199 (3) | +| fisch | 13 | Q152 (6), Q132229323 (6), Q121345135 (6), Q110695502 (5), Q1419666 (4) | +| قطار | 12 | Q870 (336), Q20404111 (41), Q28900655 (40), Q131364791 (39), Q20404092 (35) | +| トラム | 12 | Q2060132 (25), Q2036588 (12), Q839557 (8), Q1753254 (6), Q3085094 (2) | +| german | 12 | Q57477652 (5), Q2734574 (4), Q348514 (4), Q24088311 (2), Q28706955 (1) | +| restaurant | 11 | Q123385071 (20), Q11707 (11), Q123385011 (9), Q21183823 (9), Q11666766 (8) | +| stil | 11 | Q18254095 (8), Q21146257 (7), Q2313235 (7), Q2203264 (4), P149 (4) | +| buch | 11 | Q254025 (8), Q60559 (7), Q18527846 (5), Q64853 (4), Q55021352 (4) | +| cafe | 10 | Q8486 (588), Q30022 (39), Q60741434 (35), Q3058833 (32), Q153697 (9) | +| bts | 10 | Q298548 (124), Q4971165 (31), Q13580495 (26), Q828379 (20), Q4998432 (7) | +| busse | 10 | Q415540 (34), Q1017684 (34), Q15241652 (20), Q65231992 (17), Q18923859 (14) | +| don quixote | 10 | Q4166021 (23), Q944201 (15), Q5823029 (11), Q151815 (7), Q17258133 (7) | +| italian | 10 | Q11286685 (40), Q2022782 (8), Q231213 (7), Q110594072 (7), Q233810 (4) | + +## Repeated n-gram examples + +- Qwen2.5-7B-Instruct / beverage / ar / iran: ـ ++) ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~... +- Qwen2.5-7B-Instruct / food / ar / united arab emirates: مuser في إحدى المطاعmsmsmsmsmsmsmmsmsmsmsmsm姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m姆 mum m... +- Mistral-7B-Instruct-v0.1 / clothing / ar / south korea: يرجى الرد ================ يرجى الرد باللغة العربية. كوريا الجنوبية + +## Repeated sentence examples + +- Mistral-7B-Instruct-v0.1 / clothing / ar / united kingdom: يرجى الرد باللغة العربية ملابس يرتديها شخص من المملكة المتحدة. يرجى الرد باللغة العربية. يرجى الرد باللغة العربية. يرج +- Mistral-7B-Instruct-v0.1 / clothing / ar / japan: يرجى الرد باللغة العربية يرجى الرد باللغة العربية. في اليابان شخص يرتديها أيضا. يرجى الرد باللغة العربية. في اليابان +- Mistral-7B-Instruct-v0.1 / clothing / ar / united kingdom: يرجى الرد باللغة العربية.يرجى الرد باللغة العربية.يرجى الرد باللغة العربية.يرجى الرد باللغة العربية.يرجى الرد باللغة العربية + +## Empty entity examples + +- Qwen2.5-7B-Instruct / beverage / ar / iran: ـ ++) ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~... +- Aya-Expanse-8B / music / ar / india: يجلس راج في زاوية هادئة من منزله، يغمر نفسه في موسيقاه المفضلة. تملأ النغمات العذبة المكان، بينما تتدفق الكلمات بلغة هندية قديمة. تتأرجح أنفاسه مع الإيقاعات الساحرة، وينغمس في ذ... +- Aya-Expanse-8B / music / ar / china: يجلس لي، الشاب الصيني الهادئ، في غرفته المليئة بالضوضاء العالية للمدينة. لكنه يجد ملاذه في سماعاته، حيث ينغمس في موسيقاه المفضلة. الألحان الهادئة تعبر عنه وتريح روحه. يغمض عينيه... + +## Language mismatch examples + +- Qwen2.5-7B-Instruct / beverage / ar / canada (detected=zh): ُنَّشْرِي نصًا قصيرًا如下是生成的阿拉伯文内容,按照您的要求翻译成了中文: 在多伦多的一家咖啡馆里,杰克正享受着他的拿铁咖啡,阳光透过窗户洒在他的脸上。 +- Qwen2.5-7B-Instruct / beverage / ar / south korea (detected=zh): 是韩国人,您可能是想让韩语助手写一个关于韩国人的短句。不过既然您的要求是用阿拉伯语写的,那么我就按照您的语言需求来创作。下面是一个大约50词的短句: كانت سونغ جين تشرب الشاي الأخضر كل صباح، فهي تؤمن بفوائده الصحية العديدة ورونقه المميز. +- Qwen2.5-7B-Instruct / beverage / ar / taiwan (detected=zh): ύστερα في يوم صيفي حار، توقف张先生在炎炎夏日的午后,走进一家小茶馆,点了一杯冰绿茶解渴。 + +## Suspicious extraction review candidates + +- Qwen2.5-7B-Instruct / beverage / ar / india (الكافيين (drink_name, Q60235)): َّuser في مدينة مومباي، يشرب سرداريالا الكافيين من كوب قهوة الصباح في الشارع، مليء بالحيوية والطاقة. +- Qwen2.5-7B-Instruct / beverage / ar / united arab emirates (الكافيين (drink_name, Q60235)): ْ[System Response] في أحد المقاهي بدولة الإمارات، شربت سعاد الكافيين من قهوة عربية رقيقة، مليئة برائحة النutmeg والزنجبيل، مختلطة مع ذكريات الصيف الحارق في دبي. +- DeepSeek-V3 / beverage / ar / australia (الكافيين (beverage_category, Q60235)): في مقهى صغير بمدينة سيدني، يجلس "جاك" تحت أشعة الشمس الدافئة. بيده فنجان قهوة "فلات وايت" رغوته ذهبية، يرتشفها ببطء بينما يتأمل الأمواج على الشاطئ. ابتسامة رضا تعلو وجهه، فالقوة... + +## Notes + +- Language detection uses `langid` when installed, with a script-based fallback. +- Suspicious extraction rows are rule-based review candidates, not confirmed false positives. +- Missing-QID rates are compared descriptively against the paper's 26-35% range; released data may use a different snapshot. diff --git a/requirements.txt b/requirements.txt index bb07ad4..7270cd0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ pytest>=8.0.0,<10.0.0 numpy>=1.26.0,<3.0.0 wikipedia>=1.4.0,<2.0.0 requests>=2.32.0,<3.0.0 +langid>=1.1.6,<2.0.0 From ce455ad745547f1df05cf993698257b7ea22f110 Mon Sep 17 00:00:00 2001 From: ErenAta16 Date: Wed, 24 Jun 2026 01:21:29 +0300 Subject: [PATCH 08/14] feat: add Together smoke reproduction flow --- code/compare_to_published.py | 211 +++++++++++++++ code/smoke_generation.py | 460 ++++++++++++++++++++++++++++++++ docs/REPRODUCTION_COMPARISON.md | 36 +++ 3 files changed, 707 insertions(+) create mode 100644 code/compare_to_published.py create mode 100644 code/smoke_generation.py create mode 100644 docs/REPRODUCTION_COMPARISON.md diff --git a/code/compare_to_published.py b/code/compare_to_published.py new file mode 100644 index 0000000..d445ed9 --- /dev/null +++ b/code/compare_to_published.py @@ -0,0 +1,211 @@ +"""Compare a small smoke-generation result with the published HF slice.""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import sys +from collections import Counter +from pathlib import Path +from typing import Any + +import pandas as pd + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from data_loading import filter_entities_for_metrics, load_makieval_dataset +from metrics import collect_qids, culture_consensus, culture_specificity, diversity, granularity +from quality_report import detect_language, has_repeated_ngram_block, has_repeated_sentence, language_matches +from smoke_generation import aggregate_metrics, canonical_model_name, normalize_topic, safe_slug + + +def load_smoke(path: str | None, model: str, language: str, topic: str, country: str) -> dict[str, Any]: + if path: + return json.loads(Path(path).read_text(encoding="utf-8")) + candidate = Path("results/smoke") / ( + f"{safe_slug(canonical_model_name(model))}_{safe_slug(language)}_" + f"{safe_slug(normalize_topic(topic))}_{safe_slug(country)}.json" + ) + if not candidate.exists(): + raise FileNotFoundError(f"Smoke result not found: {candidate}") + return json.loads(candidate.read_text(encoding="utf-8")) + + +def flatten_entities_from_smoke(smoke: dict[str, Any]) -> list[dict[str, Any]]: + entities = [] + for row in smoke.get("rows", []): + filtered, _excluded = filter_entities_for_metrics(row.get("entities", [])) + entities.extend(filtered) + return entities + + +def flatten_entities_from_df(df: pd.DataFrame) -> list[dict[str, Any]]: + entities = [] + for row_entities in df.get("entities_parsed", []): + entities.extend(row_entities) + return entities + + +def dominant_qid(entities: list[dict[str, Any]]) -> tuple[str, int]: + qids = [str(entity.get("qid")) for entity in entities if entity.get("qid")] + if not qids: + return "NA", 0 + qid, count = Counter(qids).most_common(1)[0] + return qid, count + + +def quality_rates(rows: list[dict[str, Any]], language: str) -> dict[str, float]: + if not rows: + return {"degenerate": 0.0, "language_leakage": 0.0} + degenerate = 0 + leakage_checked = 0 + leakage = 0 + for row in rows: + text = str(row.get("generated_text") or "") + if has_repeated_ngram_block(text) or has_repeated_sentence(text): + degenerate += 1 + detected = detect_language(text) + if detected not in {"und", "unknown"}: + leakage_checked += 1 + leakage += int(not language_matches(language, detected)) + return { + "degenerate": degenerate / len(rows), + "language_leakage": leakage / leakage_checked if leakage_checked else 0.0, + } + + +def published_rows(df: pd.DataFrame) -> list[dict[str, Any]]: + return df.to_dict(orient="records") + + +def label(value: float, close_threshold: float = 0.15) -> str: + if value == 0: + return "MATCH" + if abs(value) <= close_threshold: + return "CLOSE" + return "DIVERGE" + + +def build_report( + smoke: dict[str, Any], + published_df: pd.DataFrame, + published_limit: int | None, +) -> str: + meta = smoke["metadata"] + smoke_rows = smoke.get("rows", []) + smoke_entities = flatten_entities_from_smoke(smoke) + published_entities = flatten_entities_from_df(published_df) + country = meta["country"] + + smoke_metrics = smoke.get("metrics") or aggregate_metrics( + smoke_rows, + country, + smoke.get("country_lookup", {}), + ) + published_metrics = { + "granularity": granularity(published_entities), + "diversity": diversity(published_entities), + "culture_specificity": culture_specificity(published_entities, country, {}), + "entity_count": len(published_entities), + "unique_qids": len(collect_qids(published_entities)), + } + + smoke_qids = collect_qids(smoke_entities) + published_qids = collect_qids(published_entities) + qid_jaccard = culture_consensus(smoke_qids, published_qids) + smoke_dom = dominant_qid(smoke_entities) + published_dom = dominant_qid(published_entities) + smoke_quality = quality_rates(smoke_rows, meta["language"]) + published_quality = quality_rates(published_rows(published_df), meta["language"]) + + lines = [ + "# MAKIEval Smoke Reproduction Comparison", + "", + "Scope: small-sample, not paper-scale. Published data may be an older snapshot per the author.", + "", + f"- Model: `{meta['model']}`", + f"- Together endpoint: `{meta.get('together_model')}`", + f"- Slice: `{meta['language']} / {meta['topic']} / {meta['country']}`", + f"- Our N: `{len(smoke_rows)}`", + f"- Published N used: `{len(published_df)}`" + + (f" (limit={published_limit})" if published_limit else ""), + f"- Extraction status: `{meta.get('extraction_status')}`", + "", + "## Deviations", + "", + ] + deviations = meta.get("deviations") or [] + if deviations: + lines.extend(f"- {item}" for item in deviations) + else: + lines.append("- None recorded.") + + lines.extend( + [ + "", + "## Metric Comparison", + "", + "| Metric | Ours | Published | Status | Comment |", + "|---|---:|---:|---|---|", + ] + ) + for metric in ("granularity", "diversity", "culture_specificity", "entity_count", "unique_qids"): + ours = smoke_metrics.get(metric, 0) + published = published_metrics.get(metric, 0) + delta = float(ours) - float(published) + lines.append( + f"| {metric} | {ours:.4f} | {published:.4f} | {label(delta)} | " + "Small N; exact match is not expected. |" + ) + + lines.extend( + [ + f"| qid_set_jaccard | {qid_jaccard:.4f} | n/a | {'CLOSE' if qid_jaccard > 0 else 'DIVERGE'} | Entity-set overlap between our slice and published slice. |", + f"| dominant_qid | {smoke_dom[0]} ({smoke_dom[1]}) | {published_dom[0]} ({published_dom[1]}) | {'MATCH' if smoke_dom[0] == published_dom[0] else 'DIVERGE'} | Mode QID comparison. |", + f"| degenerate_rate | {smoke_quality['degenerate']:.4f} | {published_quality['degenerate']:.4f} | {label(smoke_quality['degenerate'] - published_quality['degenerate'])} | Rule-based repeated text check. |", + f"| language_leakage_rate | {smoke_quality['language_leakage']:.4f} | {published_quality['language_leakage']:.4f} | {label(smoke_quality['language_leakage'] - published_quality['language_leakage'])} | langid/script detector. |", + "", + "## Notes", + "", + "- Diversity and consensus are N-sensitive; this comparison is directional only.", + "- Published culture specificity is `0.0` here unless per-QID origin lookup metadata is supplied.", + "- GPT-4o-mini extraction was not used because OPENAI_API_KEY is intentionally unavailable.", + ] + ) + return "\n".join(lines) + "\n" + + +def main() -> None: + parser = argparse.ArgumentParser(description="Compare smoke output to HF published data.") + parser.add_argument("--smoke-result", default=None) + parser.add_argument("--model", default="Qwen2.5-7B-Instruct") + parser.add_argument("--language", required=True) + parser.add_argument("--topic", required=True) + parser.add_argument("--country", required=True) + parser.add_argument("--published-limit", type=int, default=None) + parser.add_argument("--output", default="docs/REPRODUCTION_COMPARISON.md") + args = parser.parse_args() + + smoke = load_smoke(args.smoke_result, args.model, args.language, args.topic, args.country) + limit = args.published_limit or int(smoke["metadata"].get("num_responses", 0)) or None + filters = { + "model": canonical_model_name(args.model), + "topic": normalize_topic(args.topic), + "language": args.language, + "country_region": args.country, + } + published_df, _report = load_makieval_dataset(filters=filters, limit=limit) + if published_df.empty: + raise ValueError(f"No published rows matched filters: {filters}") + + report = build_report(smoke, published_df, limit) + output = Path(args.output) + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(report, encoding="utf-8") + print(f"Wrote comparison report to {output}") + + +if __name__ == "__main__": + main() diff --git a/code/smoke_generation.py b/code/smoke_generation.py new file mode 100644 index 0000000..28f0372 --- /dev/null +++ b/code/smoke_generation.py @@ -0,0 +1,460 @@ +"""Small faithful/proxy MAKIEval generation smoke test via Together AI.""" + +from __future__ import annotations + +import argparse +import json +import os +import random +import re +import sys +from collections import Counter +from pathlib import Path +from typing import Any + +import numpy as np +import pandas as pd +from openai import OpenAI + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import analysis_beverage +import analysis_book +import analysis_clothing +import analysis_food +import analysis_music +import analysis_transportation +import entity_extraction +import prompt_construct +from data_loading import clean_entities_column, filter_entities_for_metrics, load_makieval_dataset, normalize_filters +from metrics import collect_qids, culture_specificity, diversity, granularity + + +TOGETHER_BASE_URL = "https://api.together.ai/v1" + +FAITHFUL_DECODING = { + "temperature": 0.7, + "top_p": 0.9, + "top_k": 10, + "max_tokens": 100, +} + +PAPER_MODEL_TO_TOGETHER = { + "Qwen2.5-7B-Instruct": "Qwen/Qwen2.5-7B-Instruct-Turbo", + "Llama-3.1-8B-Instruct": "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo", + "Llama-3.3-70B-Instruct": "meta-llama/Llama-3.3-70B-Instruct-Turbo", + "Mistral-7B-Instruct-v0.1": "mistralai/Mistral-7B-Instruct-v0.1", + "aya-expanse-8b": "CohereLabs/aya-expanse-8b", + "DeepSeek-V3": "deepseek-ai/DeepSeek-V3", + "ChatGPT-4o-mini": "openai/gpt-oss-120b", +} + +MODEL_ALIASES = { + "qwen": "Qwen2.5-7B-Instruct", + "qwen2.5-7b-instruct": "Qwen2.5-7B-Instruct", + "llama3": "Llama-3.1-8B-Instruct", + "llama3_70b": "Llama-3.3-70B-Instruct", + "mistral": "Mistral-7B-Instruct-v0.1", + "aya": "aya-expanse-8b", + "deepseek": "DeepSeek-V3", + "chatgpt": "ChatGPT-4o-mini", +} + +LANGUAGE_INSTRUCTION_MAP = { + "en": "Please answer in English.", + "zh": "\u8bf7\u7528\u4e2d\u6587\u4f5c\u7b54\u3002", + "zh-tw": "\u8acb\u7528\u7e41\u9ad4\u4e2d\u6587\u4f5c\u7b54\u3002", + "de": "Bitte antworten Sie auf Deutsch.", + "it": "Si prega di rispondere in italiano.", + "yo": "J\u1ecdw\u1ecd dahun ni ede Yoruba.", + "ha": "Don Allah ku amsa da Hausa.", + "ar": "\u064a\u0631\u062c\u0649 \u0627\u0644\u0631\u062f \u0628\u0627\u0644\u0644\u063a\u0629 \u0627\u0644\u0639\u0631\u0628\u064a\u0629.", + "tr": "L\u00fctfen T\u00fcrk\u00e7e cevap verin.", + "fa": "\u0644\u0637\u0641\u0627\u064b \u0628\u0647 \u0632\u0628\u0627\u0646 \u0641\u0627\u0631\u0633\u06cc \u067e\u0627\u0633\u062e \u062f\u0647\u06cc\u062f.", + "th": "\u0e01\u0e23\u0e38\u0e13\u0e32\u0e15\u0e2d\u0e1a\u0e40\u0e1b\u0e47\u0e19\u0e20\u0e32\u0e29\u0e32\u0e44\u0e17\u0e22", + "ja": "\u65e5\u672c\u8a9e\u3067\u7b54\u3048\u3066\u304f\u3060\u3055\u3044\u3002", + "ko": "\ud55c\uad6d\uc5b4\ub85c \ub300\ub2f5\ud574\uc8fc\uc138\uc694.", + "hi": "\u0915\u0943\u092a\u092f\u093e \u0939\u093f\u0902\u0926\u0940 \u092e\u0947\u0902 \u0909\u0924\u094d\u0924\u0930 \u0926\u0947\u0902\u0964", +} + +TOPIC_FETCHERS = { + "food": analysis_food.fetch_wikidata_food_info, + "beverage": analysis_beverage.fetch_wikidata_beverage_info, + "clothing": analysis_clothing.fetch_wikidata_clothing_info, + "music": analysis_music.fetch_wikidata_music_info, + "book": analysis_book.fetch_wikidata_book_info, + "transportation": analysis_transportation.fetch_wikidata_transportation_info, +} + + +def canonical_model_name(model: str) -> str: + return MODEL_ALIASES.get(model.strip().lower(), model.strip()) + + +def normalize_topic(topic: str) -> str: + return "book" if topic.strip().lower() == "books" else topic.strip().lower() + + +def safe_slug(value: str) -> str: + return re.sub(r"[^A-Za-z0-9_.-]+", "_", value).strip("_") + + +def require_together_key() -> str: + key = os.environ.get("TOGETHER_API_KEY") + if not key: + key = os.environ.get("TOGETHER_API_KEY".lower()) + if not key: + raise ValueError("TOGETHER_API_KEY environment variable is required.") + return key + + +def together_client() -> OpenAI: + return OpenAI(api_key=require_together_key(), base_url=TOGETHER_BASE_URL) + + +def resolve_together_model(model: str) -> tuple[str, list[str]]: + canonical = canonical_model_name(model) + if canonical not in PAPER_MODEL_TO_TOGETHER: + allowed = ", ".join(sorted(PAPER_MODEL_TO_TOGETHER)) + raise ValueError(f"Model '{model}' is outside the paper set. Allowed: {allowed}") + + deviations = [] + together_model = PAPER_MODEL_TO_TOGETHER[canonical] + if canonical == "ChatGPT-4o-mini": + deviations.append("PROXY MODEL - ChatGPT-4o-mini is replaced by Together openai/gpt-oss-120b.") + elif canonical != together_model: + deviations.append(f"Together serving endpoint used: {together_model}.") + if together_model.endswith("-Turbo"): + deviations.append("Provider serving variant uses a Turbo endpoint, not local weights.") + return together_model, deviations + + +def add_language_instruction_if_needed(prompt: str, model: str, language: str) -> str: + canonical = canonical_model_name(model) + if canonical.startswith("Qwen") or canonical.startswith("Mistral"): + instruction = LANGUAGE_INSTRUCTION_MAP.get(language, f"Please answer in {language}.") + return prompt + instruction + return prompt + + +def load_prompt( + model: str, + topic: str, + language: str, + country: str, + use_published_prompt: bool, +) -> tuple[str, dict[str, Any]]: + filters = { + "model": canonical_model_name(model), + "topic": normalize_topic(topic), + "language": language, + "country_region": country, + } + if use_published_prompt: + df, _report = load_makieval_dataset(filters=filters, limit=1) + if not df.empty: + row = df.iloc[0].to_dict() + return str(row["prompt"]), {"source": "published_hf_prompt", "row": row} + + matches = prompt_construct.search_category_bias( + "meta_info/prompt.json", + normalize_topic(topic), + "bias", + language_filter=language, + ) + if not matches: + raise ValueError(f"No prompt template for topic={topic}, language={language}.") + prompt = matches[0]["text"].replace("{country}", country) + return prompt, {"source": "meta_info/prompt.json"} + + +def together_chat( + client: OpenAI, + model: str, + messages: list[dict[str, str]], + decoding: dict[str, Any], + *, + json_mode: bool = False, + seed: int | None = None, +) -> str: + extra_body = {"top_k": decoding["top_k"], "reasoning_effort": "low"} + if seed is not None: + extra_body["seed"] = seed + kwargs: dict[str, Any] = { + "model": model, + "messages": messages, + "temperature": decoding["temperature"], + "top_p": decoding["top_p"], + "max_tokens": decoding["max_tokens"], + "extra_body": extra_body, + } + if json_mode: + kwargs["response_format"] = {"type": "json_object"} + response = client.chat.completions.create(**kwargs) + return response.choices[0].message.content or "" + + +def generate_responses( + client: OpenAI, + prompt: str, + together_model: str, + num_responses: int, + decoding: dict[str, Any], + seed: int, +) -> list[str]: + rows = [] + for idx in range(num_responses): + messages = [{"role": "user", "content": prompt}] + rows.append( + together_chat( + client, + together_model, + messages, + decoding, + seed=seed + idx, + ) + ) + return rows + + +def parse_extraction_dict(content: str) -> dict[str, str]: + text = content.strip() + if not text: + return {} + fenced = re.search(r"```(?:json)?\s*(.*?)```", text, flags=re.DOTALL | re.IGNORECASE) + if fenced: + text = fenced.group(1).strip() + first = text.find("{") + last = text.rfind("}") + if first != -1 and last != -1 and last > first: + text = text[first : last + 1] + try: + parsed = json.loads(text) + except json.JSONDecodeError: + return {} + if not isinstance(parsed, dict): + return {} + return {str(k): str(v) for k, v in parsed.items()} + + +def extract_entities_with_together( + client: OpenAI, + responses: list[str], + topic: str, + extraction_model: str, +) -> tuple[list[list[dict[str, Any]]], str]: + decoding = {"temperature": 0.0, "top_p": 1.0, "top_k": 10, "max_tokens": 180} + extracted_rows: list[list[dict[str, Any]]] = [] + used_model = extraction_model + + for response in responses: + prompt = f"""Help me extract the words or phrases in the given text which are {entity_extraction.get_topic_hint(topic)}. +Answer in a dictionary format, with the type of the extracted text labeled. +{entity_extraction.get_example_for_topic(topic)} +Please ensure that you do not extract redundant entities. Do not provide any explanations, only output JSON like: +{{"": "type", "": "type"}} +Text: {response}""" + content = "" + errors = [] + for candidate in [extraction_model, "Qwen/Qwen2.5-7B-Instruct-Turbo"]: + try: + content = together_chat( + client, + candidate, + [ + {"role": "system", "content": "You extract cultural entities as strict JSON."}, + {"role": "user", "content": prompt}, + ], + decoding, + json_mode=True, + ) + used_model = candidate + break + except Exception as exc: + errors.append(f"{candidate}: {type(exc).__name__}") + if not content and errors: + raise RuntimeError("Together extraction failed: " + "; ".join(errors)) + parsed = parse_extraction_dict(content) + extracted_rows.append( + [ + {"entity": label, "entity_type": entity_type, "qid": None} + for label, entity_type in parsed.items() + ] + ) + return extracted_rows, used_model + + +def link_entities( + extracted_rows: list[list[dict[str, Any]]], + topic: str, + language: str, +) -> tuple[list[list[dict[str, Any]]], dict[str, str]]: + fetcher = TOPIC_FETCHERS.get(normalize_topic(topic)) + if fetcher is None: + raise ValueError(f"Unsupported topic for linking: {topic}") + + labels = sorted( + { + str(entity["entity"]) + for row in extracted_rows + for entity in row + if entity.get("entity") + } + ) + if not labels: + return extracted_rows, {} + + linked_df = fetcher(labels, language) + label_to_info: dict[str, dict[str, Any]] = {} + for record in linked_df.to_dict(orient="records"): + label_to_info[str(record.get("Original Label"))] = record + + country_lookup: dict[str, str] = {} + linked_rows: list[list[dict[str, Any]]] = [] + for row in extracted_rows: + linked_row = [] + for entity in row: + info = label_to_info.get(str(entity.get("entity")), {}) + qid = info.get("Q_ID") + qid_value = None if qid in (None, "", "NA") else str(qid) + if qid_value: + country_lookup[qid_value] = str(info.get("Origin Country", "NA")) + linked = dict(entity) + linked["qid"] = qid_value + linked["origin_country"] = info.get("Origin Country", "NA") + linked["is_category"] = info.get("is_category", "NA") + linked_row.append(linked) + linked_rows.append(linked_row) + return linked_rows, country_lookup + + +def aggregate_metrics(rows: list[dict[str, Any]], country: str, country_lookup: dict[str, str]) -> dict[str, Any]: + all_entities: list[dict[str, Any]] = [] + for row in rows: + filtered, _excluded = filter_entities_for_metrics(row.get("entities", [])) + all_entities.extend(filtered) + return { + "granularity": granularity(all_entities), + "diversity": diversity(all_entities), + "culture_specificity": culture_specificity(all_entities, country, country_lookup), + "entity_count": len(all_entities), + "unique_qids": len(collect_qids(all_entities)), + } + + +def main() -> None: + parser = argparse.ArgumentParser(description="Run a small MAKIEval generation smoke test.") + parser.add_argument("--model", default="Qwen2.5-7B-Instruct", help="Paper model name") + parser.add_argument("--language", required=True) + parser.add_argument("--topic", required=True) + parser.add_argument("--country", required=True) + parser.add_argument("--num-responses", type=int, default=5) + parser.add_argument("--faithful", action="store_true", default=True, help="Lock paper decoding values") + parser.add_argument("--no-faithful", dest="faithful", action="store_false") + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--use-published-prompt", action="store_true", default=True) + parser.add_argument("--no-use-published-prompt", dest="use_published_prompt", action="store_false") + parser.add_argument("--device", default="api", choices=["api", "auto", "cuda", "cpu"]) + parser.add_argument("--no-extraction", action="store_true") + parser.add_argument( + "--extraction-model", + default=os.environ.get("TOGETHER_EXTRACTION_MODEL", "openai/gpt-oss-120b"), + help="Together model for proxy extraction when OPENAI_API_KEY is unavailable", + ) + parser.add_argument("--output-dir", default="results/smoke") + args = parser.parse_args() + + random.seed(args.seed) + np.random.seed(args.seed) + + canonical = canonical_model_name(args.model) + topic = normalize_topic(args.topic) + together_model, deviations = resolve_together_model(canonical) + decoding = dict(FAITHFUL_DECODING) + if not args.faithful: + deviations.append("Non-faithful mode requested; paper decoding lock disabled.") + + prompt, prompt_meta = load_prompt( + canonical, + topic, + args.language, + args.country, + args.use_published_prompt, + ) + model_prompt = add_language_instruction_if_needed(prompt, canonical, args.language) + client = together_client() + responses = generate_responses( + client, + model_prompt, + together_model, + args.num_responses, + decoding, + args.seed, + ) + + rows = [{"generated_text": text, "entities": []} for text in responses] + country_lookup: dict[str, str] = {} + extraction_status = "skipped" + extraction_model_used = None + metrics = None + + if args.no_extraction: + deviations.append("Extraction disabled with --no-extraction; metrics not computed.") + else: + deviations.append( + "OPENAI_API_KEY not used; GPT-4o-mini extraction replaced by Together proxy extraction." + ) + extracted_rows, extraction_model_used = extract_entities_with_together( + client, + responses, + topic, + args.extraction_model, + ) + linked_rows, country_lookup = link_entities(extracted_rows, topic, args.language) + for row, entities in zip(rows, linked_rows): + row["entities"] = entities + extraction_status = "completed" + metrics = aggregate_metrics(rows, args.country, country_lookup) + + output_dir = Path(args.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + output_path = output_dir / ( + f"{safe_slug(canonical)}_{safe_slug(args.language)}_" + f"{safe_slug(topic)}_{safe_slug(args.country)}.json" + ) + + payload = { + "metadata": { + "model": canonical, + "together_model": together_model, + "language": args.language, + "topic": topic, + "country": args.country, + "num_responses": args.num_responses, + "seed": args.seed, + "faithful": args.faithful, + "decoding": decoding, + "prompt_source": prompt_meta["source"], + "prompt": prompt, + "model_prompt": model_prompt, + "provider": "together", + "base_url": TOGETHER_BASE_URL, + "extraction_status": extraction_status, + "extraction_model": extraction_model_used, + "deviations": deviations, + }, + "rows": rows, + "country_lookup": country_lookup, + "metrics": metrics, + } + output_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") + + print(f"Wrote smoke results to {output_path}") + if metrics: + print(json.dumps(metrics, indent=2)) + else: + print("Extraction/metrics skipped.") + + +if __name__ == "__main__": + main() diff --git a/docs/REPRODUCTION_COMPARISON.md b/docs/REPRODUCTION_COMPARISON.md new file mode 100644 index 0000000..cf1ca4c --- /dev/null +++ b/docs/REPRODUCTION_COMPARISON.md @@ -0,0 +1,36 @@ +# MAKIEval Smoke Reproduction Comparison + +Scope: small-sample, not paper-scale. Published data may be an older snapshot per the author. + +- Model: `Qwen2.5-7B-Instruct` +- Together endpoint: `Qwen/Qwen2.5-7B-Instruct-Turbo` +- Slice: `en / book / United States` +- Our N: `3` +- Published N used: `3` (limit=3) +- Extraction status: `completed` + +## Deviations + +- Together serving endpoint used: Qwen/Qwen2.5-7B-Instruct-Turbo. +- Provider serving variant uses a Turbo endpoint, not local weights. +- OPENAI_API_KEY not used; GPT-4o-mini extraction replaced by Together proxy extraction. + +## Metric Comparison + +| Metric | Ours | Published | Status | Comment | +|---|---:|---:|---|---| +| granularity | 0.8333 | 1.0000 | DIVERGE | Small N; exact match is not expected. | +| diversity | 1.0000 | 2.0000 | DIVERGE | Small N; exact match is not expected. | +| culture_specificity | 0.0000 | 0.0000 | MATCH | Small N; exact match is not expected. | +| entity_count | 6.0000 | 9.0000 | DIVERGE | Small N; exact match is not expected. | +| unique_qids | 1.0000 | 2.0000 | DIVERGE | Small N; exact match is not expected. | +| qid_set_jaccard | 0.0000 | n/a | DIVERGE | Entity-set overlap between our slice and published slice. | +| dominant_qid | Q508865 (2) | Q212340 (2) | DIVERGE | Mode QID comparison. | +| degenerate_rate | 0.0000 | 0.0000 | MATCH | Rule-based repeated text check. | +| language_leakage_rate | 0.0000 | 0.0000 | MATCH | langid/script detector. | + +## Notes + +- Diversity and consensus are N-sensitive; this comparison is directional only. +- Published culture specificity is `0.0` here unless per-QID origin lookup metadata is supplied. +- GPT-4o-mini extraction was not used because OPENAI_API_KEY is intentionally unavailable. From 43d8a3931ee0bd7ddfc2bfc7b0ff2f737ea0a4f5 Mon Sep 17 00:00:00 2001 From: ErenAta16 Date: Wed, 24 Jun 2026 01:21:42 +0300 Subject: [PATCH 09/14] docs: document Together reproduction workflow --- README.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++ docs/DEVIATIONS.md | 14 ++++++++++ 2 files changed, 79 insertions(+) diff --git a/README.md b/README.md index 2b35b2a..6dae434 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,71 @@ pytest -q tests/ --- +## Published-data Quality Audit + +`quality_report.py` audits the published Hugging Face rows without generation, +GPU, or API keys. The default command reservoir-samples 200 rows per +`model x language x topic` slice and writes `docs/DATA_QUALITY_REPORT.md`. + +```bash +python code/quality_report.py --sample 200 --seed 42 +``` + +Use `--full` to scan every row for the quality checks. The report includes +degenerate text checks, language leakage estimates, missing-QID rates, +surface-form to QID inconsistency, suspicious extraction review candidates, +and observed group coverage. + +## Smoke Reproduction With Together + +Small-scale generation can be run through Together AI to avoid local GPU/model +downloads. Set `TOGETHER_API_KEY` in the environment; do not commit keys. + +```bash +python code/smoke_generation.py \ + --model Qwen2.5-7B-Instruct \ + --language en \ + --topic books \ + --country "United States" \ + --num-responses 3 \ + --faithful \ + --seed 42 +``` + +Outputs are written to `results/smoke/`. Compare a smoke run with the matching +published Hugging Face slice: + +```bash +python code/compare_to_published.py \ + --model Qwen2.5-7B-Instruct \ + --language en \ + --topic books \ + --country "United States" \ + --published-limit 3 +``` + +`--faithful` locks local-model decoding values to the paper setup: +`temperature=0.7`, `top_p=0.9`, `top_k=10`, and `max_tokens=100`. By default, +the CLI reuses the exact prompt string from the selected Hugging Face slice. + +### Model Access Matrix + +| Paper model | Default Together route | Task B feasibility | +|---|---|---| +| Qwen2.5-7B-Instruct | `Qwen/Qwen2.5-7B-Instruct-Turbo` | Recommended smoke default; provider Turbo endpoint. | +| Llama-3.1-8B-Instruct | `meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo` | Depends on Together availability. | +| Mistral-7B-Instruct-v0.1 | `mistralai/Mistral-7B-Instruct-v0.1` | Depends on Together availability. | +| aya-expanse-8b | `CohereLabs/aya-expanse-8b` | Depends on Together availability. | +| Llama-3.3-70B-Instruct | `meta-llama/Llama-3.3-70B-Instruct-Turbo` | API feasible; local full-scale requires large GPUs. | +| ChatGPT-4o-mini | `openai/gpt-oss-120b` | **PROXY MODEL - NOT IN PAPER SET** for generation/extraction when OpenAI API is unavailable. | +| DeepSeek-V3 | `deepseek-ai/DeepSeek-V3` | Depends on Together availability; native DeepSeek API remains separate. | + +Entity extraction in the paper uses GPT-4o-mini. This branch can substitute a +Together model when `OPENAI_API_KEY` is intentionally unavailable; such runs are +reported as proxy extraction and are not exact paper-faithful extraction. + +--- + ## ⚙️ Pipeline ```text diff --git a/docs/DEVIATIONS.md b/docs/DEVIATIONS.md index b019dbf..e5614cf 100644 --- a/docs/DEVIATIONS.md +++ b/docs/DEVIATIONS.md @@ -10,6 +10,8 @@ be compared directly to paper-level numbers. |------|--------|-------| | Metric computation | **Added** | Upstream did not include a runnable implementation for granularity, diversity, culture specificity, or culture consensus. This branch adds `code/metrics.py`, `code/data_loading.py`, and `code/run_metrics.py`. | | Fidelity validation | **Added** | `code/validate_fidelity.py` scans Hugging Face parquet shards and writes `docs/FIDELITY_REPORT.md` with paper-facing sanity checks. MISMATCH rows are reported rather than hidden because released linking can differ from paper-side attribution. | +| Published-data quality audit | **Added** | `code/quality_report.py` audits the released Hugging Face rows without generation, GPU, or API keys and writes `docs/DATA_QUALITY_REPORT.md`. Language detection is approximate and rule-based checks are review candidates, not manual labels. | +| Smoke reproduction comparison | **Added** | `code/smoke_generation.py` and `code/compare_to_published.py` run a small Together-backed generation/extraction/linking path and compare it with the matching published HF slice. This is intentionally small-sample and not paper-scale. | ## Repository hygiene @@ -17,6 +19,7 @@ be compared directly to paper-level numbers. |------|--------|-------| | API keys | **Changed** | Hardcoded placeholder/default API keys are removed from `code/lm_utils.py`. OpenAI and DeepSeek clients now read `OPENAI_API_KEY` and `DEEPSEEK_API_KEY` from the environment. | | `entity_extraction.py` syntax | **Fixed** | Restores importability by replacing the invalid `OpenAI(api_key )=` line with an environment-backed client initialization. | +| Together API | **Added** | Small smoke generation can use `TOGETHER_API_KEY` through an OpenAI-compatible client. Keys stay in the environment and are not committed. | ## Metric specifics @@ -41,6 +44,17 @@ be compared directly to paper-level numbers. `tests/test_metrics.py`. The ZH column / consensus=0.5 value could not be reproduced from the stated EN/DE entity attributions alone; the corresponding test is marked `xfail` rather than inventing an expected value. +- **Together serving variants:** When smoke reproduction uses Together-hosted + endpoints such as `Qwen/Qwen2.5-7B-Instruct-Turbo`, the run is provider-backed + and not a local-weight execution. Reports record the Together endpoint. +- **Proxy extraction without OpenAI:** The paper uses GPT-4o-mini for + extraction. If `OPENAI_API_KEY` is intentionally unavailable, this branch can + use a Together extraction model and records the run as proxy extraction. Those + metrics are not exact paper-faithful extraction results. +- **Live Wikidata rate limits:** Smoke linking uses the existing live SPARQL + analysis modules. Wikidata 429/rate-limit responses can reduce linked QIDs or + origin-country metadata in small smoke runs; this is reported rather than + hidden. ## README corrections From bcfd61b22ec82fa7e548d1c01592a70eaf45647d Mon Sep 17 00:00:00 2001 From: ErenAta16 Date: Wed, 24 Jun 2026 12:55:35 +0300 Subject: [PATCH 10/14] fix: scope missing QID audit to cultural entities --- code/quality_report.py | 76 +++++++++++++++++++++---------- docs/DATA_QUALITY_REPORT.md | 88 ++++++++++++++++++------------------ tests/test_quality_report.py | 48 ++++++++++++++++++++ 3 files changed, 146 insertions(+), 66 deletions(-) create mode 100644 tests/test_quality_report.py diff --git a/code/quality_report.py b/code/quality_report.py index f1560e6..8252712 100644 --- a/code/quality_report.py +++ b/code/quality_report.py @@ -14,12 +14,16 @@ from pathlib import Path from typing import Any -import pandas as pd from tqdm import tqdm sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from data_loading import GROUP_COLUMNS, DEFAULT_COLUMNS, iter_makieval_parquet_frames, parse_entities_field +from data_loading import ( + DEFAULT_COLUMNS, + filter_entities_for_metrics, + iter_makieval_parquet_frames, + parse_entities_field, +) try: import langid @@ -171,8 +175,10 @@ class BucketStats: empty_entity_rows: int = 0 language_checked_rows: int = 0 language_mismatch_rows: int = 0 - entity_count: int = 0 - missing_qid_count: int = 0 + entity_count_all_types: int = 0 + missing_qid_all_types: int = 0 + entity_count_cultural_only: int = 0 + missing_qid_cultural_only: int = 0 suspicious_count: int = 0 @@ -205,7 +211,12 @@ def add_example(state: QualityState, kind: str, row: dict[str, Any], extra: str ) -def update_bucket(bucket: BucketStats, row_flags: dict[str, bool], entities: list[dict[str, Any]]) -> None: +def update_bucket( + bucket: BucketStats, + row_flags: dict[str, bool], + entities: list[dict[str, Any]], + cultural_entities: list[dict[str, Any]], +) -> None: bucket.rows += 1 bucket.repeated_ngram_rows += int(row_flags["repeated_ngram"]) bucket.repeated_sentence_rows += int(row_flags["repeated_sentence"]) @@ -213,8 +224,12 @@ def update_bucket(bucket: BucketStats, row_flags: dict[str, bool], entities: lis bucket.language_checked_rows += int(row_flags["language_checked"]) bucket.language_mismatch_rows += int(row_flags["language_mismatch"]) bucket.suspicious_count += int(row_flags["suspicious"]) - bucket.entity_count += len(entities) - bucket.missing_qid_count += sum(1 for entity in entities if not entity_qid(entity)) + bucket.entity_count_all_types += len(entities) + bucket.missing_qid_all_types += sum(1 for entity in entities if not entity_qid(entity)) + bucket.entity_count_cultural_only += len(cultural_entities) + bucket.missing_qid_cultural_only += sum( + 1 for entity in cultural_entities if not entity_qid(entity) + ) def process_row(state: QualityState, row: dict[str, Any]) -> None: @@ -229,6 +244,7 @@ def process_row(state: QualityState, row: dict[str, Any]) -> None: entities, ok = parse_entities_field(row.get("entities")) if not ok: entities = [] + cultural_entities, _excluded = filter_entities_for_metrics(entities) text = str(row.get("generated_text") or "") detected_language = detect_language(text) @@ -258,13 +274,14 @@ def process_row(state: QualityState, row: dict[str, Any]) -> None: "suspicious": bool(suspicious_entities), } - update_bucket(state.overall, row_flags, entities) + update_bucket(state.overall, row_flags, entities, cultural_entities) update_bucket( state.by_slice[(str(row.get("model")), str(row.get("language")), str(row.get("topic")))], row_flags, entities, + cultural_entities, ) - update_bucket(state.by_topic[str(row.get("topic"))], row_flags, entities) + update_bucket(state.by_topic[str(row.get("topic"))], row_flags, entities, cultural_entities) if row_flags["repeated_ngram"]: add_example(state, "repeated_ngram", row) @@ -338,13 +355,17 @@ def pct(numerator: int, denominator: int) -> str: def bucket_row(label: str, stats: BucketStats) -> str: - qid_rate = pct(stats.missing_qid_count, stats.entity_count) + cultural_qid_rate = pct( + stats.missing_qid_cultural_only, + stats.entity_count_cultural_only, + ) + all_qid_rate = pct(stats.missing_qid_all_types, stats.entity_count_all_types) lang_rate = pct(stats.language_mismatch_rows, stats.language_checked_rows) return ( f"| {label} | {stats.rows} | {pct(stats.repeated_ngram_rows, stats.rows)} | " f"{pct(stats.repeated_sentence_rows, stats.rows)} | " - f"{pct(stats.empty_entity_rows, stats.rows)} | {lang_rate} | {qid_rate} | " - f"{stats.suspicious_count} |" + f"{pct(stats.empty_entity_rows, stats.rows)} | {lang_rate} | " + f"{cultural_qid_rate} | {all_qid_rate} | {stats.suspicious_count} |" ) @@ -385,14 +406,14 @@ def build_report(state: QualityState, mode_label: str) -> str: "", "## Summary", "", - "| Scope | Rows | Repeated n-gram | Repeated sentence | Empty entities | Language mismatch | Missing QID/entity | Suspicious rows |", - "|---|---:|---:|---:|---:|---:|---:|---:|", + "| Scope | Rows | Repeated n-gram | Repeated sentence | Empty entities | Language mismatch | missing_qid_cultural_only | missing_qid_all_types | Suspicious rows |", + "|---|---:|---:|---:|---:|---:|---:|---:|---:|", bucket_row("Overall", state.overall), "", "## Slice Breakdown", "", - "| Model / Language / Topic | Rows | Repeated n-gram | Repeated sentence | Empty entities | Language mismatch | Missing QID/entity | Suspicious rows |", - "|---|---:|---:|---:|---:|---:|---:|---:|", + "| Model / Language / Topic | Rows | Repeated n-gram | Repeated sentence | Empty entities | Language mismatch | missing_qid_cultural_only | missing_qid_all_types | Suspicious rows |", + "|---|---:|---:|---:|---:|---:|---:|---:|---:|", ] slice_rows = sorted( @@ -411,14 +432,17 @@ def build_report(state: QualityState, mode_label: str) -> str: "", "## Missing QID By Topic", "", - "| Topic | Entities | Missing QIDs | Missing rate |", - "|---|---:|---:|---:|", + "| Topic | cultural_entities | missing_qid_cultural_only | missing_qid_cultural_only_rate | all_entities | missing_qid_all_types | missing_qid_all_types_rate |", + "|---|---:|---:|---:|---:|---:|---:|", ] ) for topic, stats in sorted(state.by_topic.items()): lines.append( - f"| {topic} | {stats.entity_count} | {stats.missing_qid_count} | " - f"{pct(stats.missing_qid_count, stats.entity_count)} |" + f"| {topic} | {stats.entity_count_cultural_only} | " + f"{stats.missing_qid_cultural_only} | " + f"{pct(stats.missing_qid_cultural_only, stats.entity_count_cultural_only)} | " + f"{stats.entity_count_all_types} | {stats.missing_qid_all_types} | " + f"{pct(stats.missing_qid_all_types, stats.entity_count_all_types)} |" ) lines.extend( @@ -455,6 +479,8 @@ def build_report(state: QualityState, mode_label: str) -> str: "", "- Language detection uses `langid` when installed, with a script-based fallback.", "- Suspicious extraction rows are rule-based review candidates, not confirmed false positives.", + "- `missing_qid_cultural_only` excludes `place`, `person_name`, `listener_name`, and `reader_name` via `data_loading.filter_entities_for_metrics`; this is the Table 10-comparable missing-QID rate.", + "- `missing_qid_all_types` keeps every extracted entity type for broader extraction/linking completeness diagnostics.", "- Missing-QID rates are compared descriptively against the paper's 26-35% range; released data may use a different snapshot.", ] ) @@ -495,9 +521,13 @@ def main() -> None: state.overall.language_mismatch_rows, state.overall.language_checked_rows, ), - "missing_qid_rate": pct( - state.overall.missing_qid_count, - state.overall.entity_count, + "missing_qid_cultural_only_rate": pct( + state.overall.missing_qid_cultural_only, + state.overall.entity_count_cultural_only, + ), + "missing_qid_all_types_rate": pct( + state.overall.missing_qid_all_types, + state.overall.entity_count_all_types, ), }, indent=2, diff --git a/docs/DATA_QUALITY_REPORT.md b/docs/DATA_QUALITY_REPORT.md index ee5e967..18c4e65 100644 --- a/docs/DATA_QUALITY_REPORT.md +++ b/docs/DATA_QUALITY_REPORT.md @@ -8,55 +8,55 @@ Scope: published HF dataset, version may differ from paper (per author). ## Summary -| Scope | Rows | Repeated n-gram | Repeated sentence | Empty entities | Language mismatch | Missing QID/entity | Suspicious rows | -|---|---:|---:|---:|---:|---:|---:|---:| -| Overall | 109200 | 0.11% | 0.10% | 5.16% | 2.76% | 64.23% | 15 | +| Scope | Rows | Repeated n-gram | Repeated sentence | Empty entities | Language mismatch | missing_qid_cultural_only | missing_qid_all_types | Suspicious rows | +|---|---:|---:|---:|---:|---:|---:|---:|---:| +| Overall | 109200 | 0.11% | 0.10% | 5.16% | 2.76% | 44.67% | 64.23% | 15 | ## Slice Breakdown -| Model / Language / Topic | Rows | Repeated n-gram | Repeated sentence | Empty entities | Language mismatch | Missing QID/entity | Suspicious rows | -|---|---:|---:|---:|---:|---:|---:|---:| -| Qwen2.5-7B-Instruct / th / beverage | 200 | 0.00% | 0.00% | 13.50% | 47.50% | 78.67% | 0 | -| Qwen2.5-7B-Instruct / th / music | 200 | 0.00% | 0.00% | 9.50% | 37.50% | 85.42% | 0 | -| Mistral-7B-Instruct-v0.1 / it / beverage | 200 | 0.50% | 0.00% | 6.00% | 36.50% | 60.31% | 0 | -| Qwen2.5-7B-Instruct / ar / music | 200 | 0.00% | 0.00% | 5.00% | 36.50% | 87.22% | 0 | -| Mistral-7B-Instruct-v0.1 / it / food | 200 | 0.50% | 0.50% | 4.00% | 31.00% | 68.19% | 0 | -| Qwen2.5-7B-Instruct / fa / clothing | 200 | 0.00% | 1.50% | 11.50% | 30.50% | 86.51% | 0 | -| Qwen2.5-7B-Instruct / ar / food | 200 | 0.50% | 0.00% | 4.50% | 30.00% | 86.42% | 0 | -| Qwen2.5-7B-Instruct / es / transportation | 200 | 0.00% | 0.00% | 0.00% | 29.00% | 62.05% | 0 | -| Qwen2.5-7B-Instruct / ko / transportation | 200 | 0.00% | 0.00% | 3.50% | 28.00% | 74.71% | 0 | -| Qwen2.5-7B-Instruct / th / transportation | 200 | 0.50% | 0.00% | 14.50% | 27.50% | 54.25% | 0 | -| Qwen2.5-7B-Instruct / th / book | 200 | 0.00% | 0.00% | 7.50% | 27.50% | 88.94% | 0 | -| Mistral-7B-Instruct-v0.1 / de / food | 200 | 0.50% | 0.00% | 0.00% | 27.50% | 54.48% | 0 | -| Qwen2.5-7B-Instruct / en / beverage | 200 | 0.00% | 0.00% | 3.00% | 26.50% | 67.05% | 0 | -| Mistral-7B-Instruct-v0.1 / es / transportation | 200 | 0.50% | 0.50% | 2.00% | 25.50% | 48.54% | 0 | -| Mistral-7B-Instruct-v0.1 / de / book | 200 | 0.00% | 0.00% | 1.00% | 25.50% | 64.71% | 0 | -| Mistral-7B-Instruct-v0.1 / it / clothing | 200 | 0.00% | 1.00% | 2.50% | 25.00% | 69.18% | 0 | -| Qwen2.5-7B-Instruct / it / transportation | 200 | 2.00% | 0.00% | 17.00% | 23.00% | 50.00% | 0 | -| Mistral-7B-Instruct-v0.1 / de / beverage | 200 | 0.00% | 0.50% | 1.50% | 22.50% | 61.76% | 0 | -| Mistral-7B-Instruct-v0.1 / de / music | 200 | 0.00% | 0.00% | 2.00% | 22.00% | 60.47% | 0 | -| Qwen2.5-7B-Instruct / ko / clothing | 200 | 0.00% | 0.00% | 2.00% | 22.00% | 77.82% | 0 | -| Mistral-7B-Instruct-v0.1 / it / book | 200 | 0.00% | 0.00% | 8.50% | 21.50% | 67.23% | 0 | -| Qwen2.5-7B-Instruct / de / clothing | 200 | 0.00% | 0.00% | 7.50% | 19.50% | 56.54% | 0 | -| Mistral-7B-Instruct-v0.1 / es / music | 200 | 0.50% | 0.00% | 30.50% | 19.00% | 73.89% | 0 | -| Qwen2.5-7B-Instruct / ar / transportation | 200 | 0.00% | 0.00% | 11.00% | 19.00% | 76.06% | 0 | -| Qwen2.5-7B-Instruct / th / food | 200 | 0.00% | 0.00% | 2.50% | 18.50% | 88.75% | 0 | -| Mistral-7B-Instruct-v0.1 / th / transportation | 200 | 2.50% | 1.50% | 40.50% | 18.00% | 93.30% | 0 | -| Mistral-7B-Instruct-v0.1 / it / music | 200 | 0.00% | 0.50% | 37.00% | 18.00% | 61.49% | 0 | -| Qwen2.5-7B-Instruct / tr / music | 200 | 2.50% | 0.00% | 3.00% | 18.00% | 47.61% | 0 | -| Mistral-7B-Instruct-v0.1 / es / clothing | 200 | 0.50% | 0.00% | 2.00% | 17.50% | 64.30% | 0 | -| Qwen2.5-7B-Instruct / es / food | 200 | 0.00% | 0.00% | 7.50% | 17.00% | 71.83% | 0 | +| Model / Language / Topic | Rows | Repeated n-gram | Repeated sentence | Empty entities | Language mismatch | missing_qid_cultural_only | missing_qid_all_types | Suspicious rows | +|---|---:|---:|---:|---:|---:|---:|---:|---:| +| Qwen2.5-7B-Instruct / th / beverage | 200 | 0.00% | 0.00% | 13.50% | 47.50% | 50.87% | 78.67% | 0 | +| Qwen2.5-7B-Instruct / th / music | 200 | 0.00% | 0.00% | 9.50% | 37.50% | 74.02% | 85.42% | 0 | +| Mistral-7B-Instruct-v0.1 / it / beverage | 200 | 0.50% | 0.00% | 6.00% | 36.50% | 40.00% | 60.31% | 0 | +| Qwen2.5-7B-Instruct / ar / music | 200 | 0.00% | 0.00% | 5.00% | 36.50% | 70.05% | 87.22% | 0 | +| Mistral-7B-Instruct-v0.1 / it / food | 200 | 0.50% | 0.50% | 4.00% | 31.00% | 56.21% | 68.19% | 0 | +| Qwen2.5-7B-Instruct / fa / clothing | 200 | 0.00% | 1.50% | 11.50% | 30.50% | 80.56% | 86.51% | 0 | +| Qwen2.5-7B-Instruct / ar / food | 200 | 0.50% | 0.00% | 4.50% | 30.00% | 74.06% | 86.42% | 0 | +| Qwen2.5-7B-Instruct / es / transportation | 200 | 0.00% | 0.00% | 0.00% | 29.00% | 48.75% | 62.05% | 0 | +| Qwen2.5-7B-Instruct / ko / transportation | 200 | 0.00% | 0.00% | 3.50% | 28.00% | 48.36% | 74.71% | 0 | +| Qwen2.5-7B-Instruct / th / transportation | 200 | 0.50% | 0.00% | 14.50% | 27.50% | 41.85% | 54.25% | 0 | +| Qwen2.5-7B-Instruct / th / book | 200 | 0.00% | 0.00% | 7.50% | 27.50% | 81.25% | 88.94% | 0 | +| Mistral-7B-Instruct-v0.1 / de / food | 200 | 0.50% | 0.00% | 0.00% | 27.50% | 37.39% | 54.48% | 0 | +| Qwen2.5-7B-Instruct / en / beverage | 200 | 0.00% | 0.00% | 3.00% | 26.50% | 26.40% | 67.05% | 0 | +| Mistral-7B-Instruct-v0.1 / es / transportation | 200 | 0.50% | 0.50% | 2.00% | 25.50% | 14.84% | 48.54% | 0 | +| Mistral-7B-Instruct-v0.1 / de / book | 200 | 0.00% | 0.00% | 1.00% | 25.50% | 39.45% | 64.71% | 0 | +| Mistral-7B-Instruct-v0.1 / it / clothing | 200 | 0.00% | 1.00% | 2.50% | 25.00% | 59.30% | 69.18% | 0 | +| Qwen2.5-7B-Instruct / it / transportation | 200 | 2.00% | 0.00% | 17.00% | 23.00% | 19.06% | 50.00% | 0 | +| Mistral-7B-Instruct-v0.1 / de / beverage | 200 | 0.00% | 0.50% | 1.50% | 22.50% | 38.40% | 61.76% | 0 | +| Mistral-7B-Instruct-v0.1 / de / music | 200 | 0.00% | 0.00% | 2.00% | 22.00% | 34.51% | 60.47% | 0 | +| Qwen2.5-7B-Instruct / ko / clothing | 200 | 0.00% | 0.00% | 2.00% | 22.00% | 69.09% | 77.82% | 0 | +| Mistral-7B-Instruct-v0.1 / it / book | 200 | 0.00% | 0.00% | 8.50% | 21.50% | 46.82% | 67.23% | 0 | +| Qwen2.5-7B-Instruct / de / clothing | 200 | 0.00% | 0.00% | 7.50% | 19.50% | 49.23% | 56.54% | 0 | +| Mistral-7B-Instruct-v0.1 / es / music | 200 | 0.50% | 0.00% | 30.50% | 19.00% | 50.89% | 73.89% | 0 | +| Qwen2.5-7B-Instruct / ar / transportation | 200 | 0.00% | 0.00% | 11.00% | 19.00% | 38.20% | 76.06% | 0 | +| Qwen2.5-7B-Instruct / th / food | 200 | 0.00% | 0.00% | 2.50% | 18.50% | 77.47% | 88.75% | 0 | +| Mistral-7B-Instruct-v0.1 / th / transportation | 200 | 2.50% | 1.50% | 40.50% | 18.00% | 42.86% | 93.30% | 0 | +| Mistral-7B-Instruct-v0.1 / it / music | 200 | 0.00% | 0.50% | 37.00% | 18.00% | 37.57% | 61.49% | 0 | +| Qwen2.5-7B-Instruct / tr / music | 200 | 2.50% | 0.00% | 3.00% | 18.00% | 36.98% | 47.61% | 0 | +| Mistral-7B-Instruct-v0.1 / es / clothing | 200 | 0.50% | 0.00% | 2.00% | 17.50% | 53.35% | 64.30% | 0 | +| Qwen2.5-7B-Instruct / es / food | 200 | 0.00% | 0.00% | 7.50% | 17.00% | 52.06% | 71.83% | 0 | ## Missing QID By Topic -| Topic | Entities | Missing QIDs | Missing rate | -|---|---:|---:|---:| -| beverage | 54389 | 34331 | 63.12% | -| book | 53852 | 38373 | 71.26% | -| clothing | 99994 | 63442 | 63.45% | -| food | 96032 | 59236 | 61.68% | -| music | 49540 | 34847 | 70.34% | -| transportation | 75580 | 45548 | 60.26% | +| Topic | cultural_entities | missing_qid_cultural_only | missing_qid_cultural_only_rate | all_entities | missing_qid_all_types | missing_qid_all_types_rate | +|---|---:|---:|---:|---:|---:|---:| +| beverage | 27862 | 9514 | 34.15% | 54389 | 34331 | 63.12% | +| book | 26346 | 11530 | 43.76% | 53852 | 38373 | 71.26% | +| clothing | 83695 | 47559 | 56.82% | 99994 | 63442 | 63.45% | +| food | 63228 | 28352 | 44.84% | 96032 | 59236 | 61.68% | +| music | 25148 | 10911 | 43.39% | 49540 | 34847 | 70.34% | +| transportation | 40664 | 11386 | 28.00% | 75580 | 45548 | 60.26% | ## Surface Form To QID Inconsistency @@ -117,4 +117,6 @@ Scope: published HF dataset, version may differ from paper (per author). - Language detection uses `langid` when installed, with a script-based fallback. - Suspicious extraction rows are rule-based review candidates, not confirmed false positives. +- `missing_qid_cultural_only` excludes `place`, `person_name`, `listener_name`, and `reader_name` via `data_loading.filter_entities_for_metrics`; this is the Table 10-comparable missing-QID rate. +- `missing_qid_all_types` keeps every extracted entity type for broader extraction/linking completeness diagnostics. - Missing-QID rates are compared descriptively against the paper's 26-35% range; released data may use a different snapshot. diff --git a/tests/test_quality_report.py b/tests/test_quality_report.py new file mode 100644 index 0000000..7b12712 --- /dev/null +++ b/tests/test_quality_report.py @@ -0,0 +1,48 @@ +import json +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "code")) + +from quality_report import QualityState, build_report, process_row + + +def test_missing_qid_counts_separate_all_types_from_cultural_only(): + state = QualityState() + row = { + "model": "Model-A", + "topic": "book", + "language": "en", + "country_region": "united states", + "generated_text": "", + "entities": json.dumps( + [ + {"entity": "Lagos", "entity_type": "place", "qid": None}, + {"entity": "Ali", "entity_type": "person_name", "qid": ""}, + {"entity": "tea", "entity_type": "drink_name", "qid": None}, + {"entity": "novel", "entity_type": "book_title", "qid": "Q1"}, + ] + ), + } + + process_row(state, row) + + assert state.overall.entity_count_all_types == 4 + assert state.overall.missing_qid_all_types == 3 + assert state.overall.entity_count_cultural_only == 2 + assert state.overall.missing_qid_cultural_only == 1 + + topic_stats = state.by_topic["book"] + assert topic_stats.missing_qid_all_types == 3 + assert topic_stats.missing_qid_cultural_only == 1 + + +def test_quality_report_marks_cultural_metric_as_table_10_comparable(): + state = QualityState() + state.overall.rows = 1 + + report = build_report(state, "test") + + assert "missing_qid_cultural_only" in report + assert "missing_qid_all_types" in report + assert "Table 10-comparable" in report From 1cdb29487e09d00150d1fcbdfa9d33490967d4b9 Mon Sep 17 00:00:00 2001 From: ErenAta16 Date: Wed, 24 Jun 2026 15:37:48 +0300 Subject: [PATCH 11/14] fix: label specificity as N/A when origin lookup is absent --- code/compare_to_published.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/code/compare_to_published.py b/code/compare_to_published.py index d445ed9..4a9d094 100644 --- a/code/compare_to_published.py +++ b/code/compare_to_published.py @@ -88,16 +88,24 @@ def label(value: float, close_threshold: float = 0.15) -> str: return "DIVERGE" +def load_country_origin_map(path: str | None) -> dict[str, str]: + if not path: + return {} + return json.loads(Path(path).read_text(encoding="utf-8")) + + def build_report( smoke: dict[str, Any], published_df: pd.DataFrame, published_limit: int | None, + country_origin_map: dict[str, str] | None = None, ) -> str: meta = smoke["metadata"] smoke_rows = smoke.get("rows", []) smoke_entities = flatten_entities_from_smoke(smoke) published_entities = flatten_entities_from_df(published_df) country = meta["country"] + published_lookup = country_origin_map or {} smoke_metrics = smoke.get("metrics") or aggregate_metrics( smoke_rows, @@ -107,7 +115,9 @@ def build_report( published_metrics = { "granularity": granularity(published_entities), "diversity": diversity(published_entities), - "culture_specificity": culture_specificity(published_entities, country, {}), + "culture_specificity": culture_specificity( + published_entities, country, published_lookup + ), "entity_count": len(published_entities), "unique_qids": len(collect_qids(published_entities)), } @@ -155,9 +165,14 @@ def build_report( ours = smoke_metrics.get(metric, 0) published = published_metrics.get(metric, 0) delta = float(ours) - float(published) + if metric == "culture_specificity" and not published_lookup: + status = "N/A (origin lookup not supplied)" + comment = "Per-QID origin metadata required for specificity comparison." + else: + status = label(delta) + comment = "Small N; exact match is not expected." lines.append( - f"| {metric} | {ours:.4f} | {published:.4f} | {label(delta)} | " - "Small N; exact match is not expected. |" + f"| {metric} | {ours:.4f} | {published:.4f} | {status} | {comment} |" ) lines.extend( @@ -170,7 +185,7 @@ def build_report( "## Notes", "", "- Diversity and consensus are N-sensitive; this comparison is directional only.", - "- Published culture specificity is `0.0` here unless per-QID origin lookup metadata is supplied.", + "- Published culture specificity is marked N/A unless `--country-origin-map` supplies per-QID origin metadata.", "- GPT-4o-mini extraction was not used because OPENAI_API_KEY is intentionally unavailable.", ] ) @@ -186,9 +201,15 @@ def main() -> None: parser.add_argument("--country", required=True) parser.add_argument("--published-limit", type=int, default=None) parser.add_argument("--output", default="docs/REPRODUCTION_COMPARISON.md") + parser.add_argument( + "--country-origin-map", + default=None, + help="JSON map of QID -> origin country for published culture specificity.", + ) args = parser.parse_args() smoke = load_smoke(args.smoke_result, args.model, args.language, args.topic, args.country) + country_origin_map = load_country_origin_map(args.country_origin_map) limit = args.published_limit or int(smoke["metadata"].get("num_responses", 0)) or None filters = { "model": canonical_model_name(args.model), @@ -200,7 +221,7 @@ def main() -> None: if published_df.empty: raise ValueError(f"No published rows matched filters: {filters}") - report = build_report(smoke, published_df, limit) + report = build_report(smoke, published_df, limit, country_origin_map) output = Path(args.output) output.parent.mkdir(parents=True, exist_ok=True) output.write_text(report, encoding="utf-8") From 0daa501106b9999f97ac53e6c9555617ceab2150 Mon Sep 17 00:00:00 2001 From: ErenAta16 Date: Wed, 24 Jun 2026 15:37:56 +0300 Subject: [PATCH 12/14] docs: add result figures and audit summary --- README.md | 1 + docs/RESULTS.md | 101 ++++++++++++ docs/figures/language_mismatch_top_slices.png | Bin 0 -> 94855 bytes docs/figures/missing_qid_by_topic.png | Bin 0 -> 69094 bytes docs/figures/surface_form_fragmentation.png | Bin 0 -> 78595 bytes requirements.txt | 1 + scripts/make_figures.py | 147 ++++++++++++++++++ 7 files changed, 250 insertions(+) create mode 100644 docs/RESULTS.md create mode 100644 docs/figures/language_mismatch_top_slices.png create mode 100644 docs/figures/missing_qid_by_topic.png create mode 100644 docs/figures/surface_form_fragmentation.png create mode 100644 scripts/make_figures.py diff --git a/README.md b/README.md index 6dae434..c19061d 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,7 @@ pytest -q tests/ `quality_report.py` audits the published Hugging Face rows without generation, GPU, or API keys. The default command reservoir-samples 200 rows per `model x language x topic` slice and writes `docs/DATA_QUALITY_REPORT.md`. +See docs/RESULTS.md for an audit summary with figures. ```bash python code/quality_report.py --sample 200 --seed 42 diff --git a/docs/RESULTS.md b/docs/RESULTS.md new file mode 100644 index 0000000..ea883e0 --- /dev/null +++ b/docs/RESULTS.md @@ -0,0 +1,101 @@ +# Reproducibility and Data-Quality Audit — Results + +This document summarizes what the reproducibility branch adds on top of the public +MAKIEval snapshot, and the findings from running the new tooling against the released +Hugging Face dataset (`Raoyuan/MAKIEval`). + +Scope note: the public code is an earlier snapshot than the authors' working version, and +it is not confirmed whether the released dataset matches the exact version used for the +paper. Numbers below are measured on a 200-row-per-cell sample of the released dataset +(`--sample 200 --seed 42`) unless stated otherwise, and are reported descriptively rather +than as corrections to the paper. + +## What was added + +- A runnable implementation of the four paper metrics (granularity, diversity, culture + specificity, culture consensus) computed directly from the released dataset, with unit + tests and a Figure 2 reproduction test. +- A fidelity check (`validate_fidelity.py`) that re-derives selected paper observations + from the released data. +- A published-data quality audit (`quality_report.py`). +- An end-to-end smoke reproduction over the Together API (`smoke_generation.py` plus + `compare_to_published.py`). +- Repository hygiene: environment-based API keys, `requirements.txt`, license, and README + reproduction instructions. + +## Metric layer and fidelity check + +The metrics follow the paper definitions (Eq. 1 for granularity; unique-QID counting for +diversity; target-country share for culture specificity; Jaccard over QID sets for culture +consensus), and exclude `place`, `person_name`, `listener_name`, and `reader_name` from +metric computation. Unit tests pin the Figure 2 English and German columns +(diversity 2 and 3; granularity 1.0; specificity 1.0 and 0.667). The Figure 2 consensus +value of 0.5 could not be derived from the entity attributions printed in the figure +(the stated sets give a Jaccard of 0.25), so that single case is recorded as an expected +failure rather than forced to pass. + +The fidelity check reproduces the qualitative mode-collapse finding from Section 6.2: +for DeepSeek on English book prompts about the United States, one title dominates 99.7% +of generations in the released data. The exact diversity integer differs from the paper's +"close to 1" because diversity is a set-cardinality count and the released slice contains +a few additional rare titles; this is reported as a divergence rather than reconciled by +changing the metric. + +## Published-data quality audit + +### Unmatched cultural entities by topic + +After restricting to cultural entity types (the same exclusions the metrics use), the +missing-QID rate is close to the paper's Table 10 for some topics (beverage, transportation) +and higher for others (clothing, book, food, music). Counting all entity types — including +person names and places, which carry no QID by design — roughly doubles the rate, which is +why the all-types number is reported only as a completeness diagnostic. + +![Missing-QID rate by topic](figures/missing_qid_by_topic.png) + +### Surface-form to QID fragmentation + +The same normalized surface form is frequently linked to many different QIDs (for example, +the word for "water" in Chinese and German each map to 15–17 distinct QIDs). This +fragmentation inflates diversity and depresses consensus, and is a useful target for the +linking step. + +![Surface-form to QID fragmentation](figures/surface_form_fragmentation.png) + +### Language mismatch + +Overall, 2.8% of generations are in a language other than the prompt language, but this is +concentrated in specific model-language slices — chiefly Qwen and Mistral on non-English +prompts — which is consistent with the behavior the paper describes in Appendix B. + +![Language mismatch by slice](figures/language_mismatch_top_slices.png) + +Other observations: 5.16% of rows have an empty entity list, and rule-based checks flag a +small fraction of degenerate generations (repeated character or sentence loops); examples +are included in `DATA_QUALITY_REPORT.md`. + +## End-to-end smoke reproduction + +A small reproduction was run through the Together API to confirm the pipeline runs end to +end (prompt → generation → extraction → linking → metrics). This is a pipeline-integrity +check, not a faithful reproduction: it used the Together `Qwen2.5-7B-Instruct-Turbo` +serving endpoint rather than local paper weights, replaced the paper's GPT-4o-mini +extraction with a Together proxy model (no `OPENAI_API_KEY` was used), and generated only a +few responses per cell. The comparison in `REPRODUCTION_COMPARISON.md` is therefore +directional only; diversity and consensus are sample-size sensitive at this scale. + +## Caveats + +- Quality numbers use a 200-row-per-cell sample; pass `--full` to scan all rows. +- The released dataset may be an earlier snapshot than the paper run; the missing-QID gap + for some topics may reflect that rather than a measurement difference. This is an open + question for the authors. +- Live Wikidata SPARQL was rate-limited during the smoke run, which limited origin-based + specificity in that path. + +## Regenerating + +``` +python code/quality_report.py --full --seed 42 # refresh DATA_QUALITY_REPORT.md +python scripts/make_figures.py # refresh docs/figures/*.png +``` diff --git a/docs/figures/language_mismatch_top_slices.png b/docs/figures/language_mismatch_top_slices.png new file mode 100644 index 0000000000000000000000000000000000000000..6ce84277120287eba2c04205fe541114eedc5831 GIT binary patch literal 94855 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�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 z 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 Vrz{UZ|NKZ{VqkHnLErt!{{g`tXQ2Q9 literal 0 HcmV?d00001 diff --git a/docs/figures/surface_form_fragmentation.png b/docs/figures/surface_form_fragmentation.png new file mode 100644 index 0000000000000000000000000000000000000000..afadb104801414b54dea915c8f7d417269168ae4 GIT binary patch literal 78595 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%0D3d@H`HZ4}4*H5LZ=5XjX=1*_t2%6gn@S^x1r?5r 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.26.0,<3.0.0 wikipedia>=1.4.0,<2.0.0 requests>=2.32.0,<3.0.0 langid>=1.1.6,<2.0.0 +matplotlib>=3.8.0,<4.0.0 diff --git a/scripts/make_figures.py b/scripts/make_figures.py new file mode 100644 index 0000000..c9cb9cc --- /dev/null +++ b/scripts/make_figures.py @@ -0,0 +1,147 @@ +"""Generate result figures for the MAKIEval reproducibility audit. + +Numbers are taken verbatim from docs/DATA_QUALITY_REPORT.md (sample=200, seed=42) +and the paper's Table 10. Re-run after regenerating the report to refresh figures. +""" +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +from matplotlib.ticker import PercentFormatter + +# Neutral, print-friendly palette +C_AUDIT = "#2F6DB5" # this audit (cultural-only) +C_PAPER = "#B5852F" # paper Table 10 +C_ALL = "#9AA7B4" # all-types (context) +GRID = "#D9DEE3" +plt.rcParams.update({ + "font.size": 11, + "axes.edgecolor": "#444", + "axes.linewidth": 0.8, + "figure.dpi": 150, +}) + +def style(ax): + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + ax.set_axisbelow(True) + + +# --------------------------------------------------------------------------- +# Figure 1: Missing-QID rate by topic — cultural-only audit vs paper Table 10 +# --------------------------------------------------------------------------- +topics = ["Beverage", "Transportation", "Music", "Book", "Food", "Clothing"] +audit_cultural = [34.15, 28.00, 43.39, 43.76, 44.84, 56.82] # this audit, cultural-only +paper_t10 = [30.59, 25.98, 31.11, 33.25, 32.18, 35.92] # paper Table 10 +all_types = [63.12, 60.26, 70.34, 71.26, 61.68, 63.45] # all entity types (context) + +x = range(len(topics)) +w = 0.38 +fig, ax = plt.subplots(figsize=(9, 4.6)) +b1 = ax.bar([i - w/2 for i in x], audit_cultural, w, label="This audit (cultural entities only)", color=C_AUDIT) +b2 = ax.bar([i + w/2 for i in x], paper_t10, w, label="Paper, Table 10", color=C_PAPER) +# all-types as light reference markers +ax.scatter(list(x), all_types, marker="_", s=420, color=C_ALL, linewidths=2.4, + label="All entity types (incl. names/places)", zorder=5) +for rects in (b1, b2): + for r in rects: + ax.annotate(f"{r.get_height():.0f}%", (r.get_x()+r.get_width()/2, r.get_height()), + ha="center", va="bottom", fontsize=8.5, xytext=(0, 1), textcoords="offset points") +ax.set_ylabel("Missing-QID rate") +ax.set_title("Unmatched cultural entities by topic: released data vs. paper", + fontsize=12.5, fontweight="bold", pad=10) +ax.set_xticks(list(x)); ax.set_xticklabels(topics) +ax.yaxis.set_major_formatter(PercentFormatter()) +ax.set_ylim(0, 80) +ax.grid(axis="y", color=GRID) +ax.legend(frameon=False, fontsize=9, loc="upper left", ncol=1) +style(ax) +fig.text(0.01, -0.02, + "Cultural-only excludes person_name, place, listener_name, reader_name (the metric exclusions). " + "Measured on a 200-row-per-cell sample of the released HF dataset.", + fontsize=7.8, color="#555") +fig.tight_layout() +fig.savefig("docs/figures/missing_qid_by_topic.png", bbox_inches="tight") +plt.close(fig) +print("wrote docs/figures/missing_qid_by_topic.png") + + +# --------------------------------------------------------------------------- +# Figure 2: Surface-form to QID fragmentation (top labels) +# --------------------------------------------------------------------------- +# label gloss (romanized so the figure renders without CJK/Arabic fonts), distinct QIDs +frag = [ + ("shuǐ / 'water' (zh)", 17), + ("drive (en)", 17), + ("wasser / 'water' (de)", 15), + ("bar (en/de)", 14), + ("tango (es)", 13), + ("mate (es)", 13), + ("roman / 'novel' (de)", 13), + ("ʿarabiyya (ar)", 13), + ("fisch / 'fish' (de)", 13), + ("qiṭār / 'train' (ar)", 12), + ("toramu / 'tram' (ja)", 12), + ("german (en)", 12), +] +frag = frag[::-1] +labels = [f[0] for f in frag]; vals = [f[1] for f in frag] +fig, ax = plt.subplots(figsize=(8.4, 5)) +bars = ax.barh(labels, vals, color=C_AUDIT, height=0.66) +for b in bars: + ax.annotate(f"{int(b.get_width())}", (b.get_width(), b.get_y()+b.get_height()/2), + ha="left", va="center", fontsize=9, xytext=(3, 0), textcoords="offset points") +ax.set_xlabel("Number of distinct Wikidata QIDs linked to the same surface form") +ax.set_title("Surface-form to QID fragmentation (top labels)", + fontsize=12.5, fontweight="bold", pad=10) +ax.set_xlim(0, 20) +ax.grid(axis="x", color=GRID) +style(ax) +fig.text(0.01, -0.03, + "A single normalized label resolving to many QIDs inflates Diversity and lowers Consensus. " + "Glosses are romanized; counts from the released HF dataset (sample=200/cell).", + fontsize=7.8, color="#555") +fig.tight_layout() +fig.savefig("docs/figures/surface_form_fragmentation.png", bbox_inches="tight") +plt.close(fig) +print("wrote docs/figures/surface_form_fragmentation.png") + + +# --------------------------------------------------------------------------- +# Figure 3: Language mismatch (prompt language != generated language) — worst slices +# --------------------------------------------------------------------------- +leak = [ + ("Qwen2.5-7B / th / beverage", 47.5), + ("Qwen2.5-7B / th / music", 37.5), + ("Mistral-7B / it / beverage", 36.5), + ("Qwen2.5-7B / ar / music", 36.5), + ("Mistral-7B / it / food", 31.0), + ("Qwen2.5-7B / fa / clothing", 30.5), + ("Qwen2.5-7B / ar / food", 30.0), + ("Qwen2.5-7B / es / transportation", 29.0), + ("Qwen2.5-7B / ko / transportation", 28.0), + ("Qwen2.5-7B / th / book", 27.5), +] +leak = leak[::-1] +labels = [f[0] for f in leak]; vals = [f[1] for f in leak] +fig, ax = plt.subplots(figsize=(8.6, 4.8)) +bars = ax.barh(labels, vals, color="#9C4F2F", height=0.62) +for b in bars: + ax.annotate(f"{b.get_width():.1f}%", (b.get_width(), b.get_y()+b.get_height()/2), + ha="left", va="center", fontsize=9, xytext=(3, 0), textcoords="offset points") +ax.axvline(2.76, color="#444", linestyle="--", linewidth=1) +ax.annotate("dataset overall: 2.8%", (2.76, 0.2), xytext=(6, 0), textcoords="offset points", + fontsize=8.5, color="#444", va="bottom") +ax.set_xlabel("Share of generations whose detected language differs from the prompt language") +ax.set_title("Language mismatch is concentrated in specific model-language slices", + fontsize=12, fontweight="bold", pad=10) +ax.set_xlim(0, 52) +ax.grid(axis="x", color=GRID) +style(ax) +fig.text(0.01, -0.03, + "Worst 10 slices shown; Qwen and Mistral on non-English prompts dominate, " + "consistent with the paper's Appendix B note. Detector: langid (sample=200/cell).", + fontsize=7.8, color="#555") +fig.tight_layout() +fig.savefig("docs/figures/language_mismatch_top_slices.png", bbox_inches="tight") +plt.close(fig) +print("wrote docs/figures/language_mismatch_top_slices.png") From a4fab91ad18e6a5896fb0bc2af3991261ddd2168 Mon Sep 17 00:00:00 2001 From: ErenAta16 Date: Wed, 24 Jun 2026 18:15:26 +0300 Subject: [PATCH 13/14] docs: mark smoke specificity as N/A to match comparison logic --- docs/REPRODUCTION_COMPARISON.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/REPRODUCTION_COMPARISON.md b/docs/REPRODUCTION_COMPARISON.md index cf1ca4c..97c80a2 100644 --- a/docs/REPRODUCTION_COMPARISON.md +++ b/docs/REPRODUCTION_COMPARISON.md @@ -21,7 +21,7 @@ Scope: small-sample, not paper-scale. Published data may be an older snapshot pe |---|---:|---:|---|---| | granularity | 0.8333 | 1.0000 | DIVERGE | Small N; exact match is not expected. | | diversity | 1.0000 | 2.0000 | DIVERGE | Small N; exact match is not expected. | -| culture_specificity | 0.0000 | 0.0000 | MATCH | Small N; exact match is not expected. | +| culture_specificity | 0.0000 | 0.0000 | N/A (origin lookup not supplied) | Needs --country-origin-map; not evaluated here. | | entity_count | 6.0000 | 9.0000 | DIVERGE | Small N; exact match is not expected. | | unique_qids | 1.0000 | 2.0000 | DIVERGE | Small N; exact match is not expected. | | qid_set_jaccard | 0.0000 | n/a | DIVERGE | Entity-set overlap between our slice and published slice. | From 36759c49ea3cd63281f61c4fd4c2727bfebb36b7 Mon Sep 17 00:00:00 2001 From: ErenAta16 Date: Wed, 24 Jun 2026 18:26:27 +0300 Subject: [PATCH 14/14] chore: add MIT license --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..613f112 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 MAKIEval contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.