Files
wehub-resource-sync 542cfa195c
CI / Frontend build (push) Failing after 9m6s
CI / Plugin validate (push) Failing after 9m27s
CI / Python lint (push) Failing after 16m1s
CI / Tests (push) Successful in 18m0s
Deploy / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:27 +08:00

148 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""Generate fake (query, chunk_path) pairs for training pipeline validation.
Randomly samples chunks from kiwix_tiles, uses article titles to create
synthetic queries, and splits into train/eval JSONL files.
Usage:
uv run python training/fake_data.py \
--tiles-dir /opt/dlami/nvme/kiwix_tiles \
--articles-json /opt/dlami/nvme/kiwix/wikipedia_en_all_maxi_2025-08.zim.articles.json \
--output-dir training/data \
--num-articles 1000
"""
import argparse
import json
import logging
import random
import re
from pathlib import Path
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger(__name__)
QUERY_TEMPLATES = [
"What is {title}?",
"Tell me about {title}",
"{title} overview",
]
def find_chunk_paths(tiles_dir: Path, article_id: int) -> list[str]:
"""Find all chunk PNG paths for a given article ID."""
shard_size = 8284
top_shard = article_id // shard_size
top_dir = tiles_dir / f"shard_{top_shard:03d}"
if not top_dir.exists():
return []
tile_dir_name = f"{article_id}.png.tiles"
# Search sub-shards
for sub in top_dir.iterdir():
if not sub.is_dir() or not sub.name.startswith("shard_"):
continue
candidate = sub / tile_dir_name
if candidate.exists():
chunks_json = candidate / "chunks.json"
if chunks_json.exists():
try:
chunks = json.loads(chunks_json.read_text())
return [
str(candidate / c["file"])
for c in chunks.get("chunks", [])
if (candidate / c["file"]).exists()
]
except (json.JSONDecodeError, KeyError):
pass
return []
def title_from_slug(slug: str) -> str:
"""Convert URL slug to readable title."""
title = slug.replace("_", " ")
# Remove URL encoding
title = re.sub(r"%[0-9A-Fa-f]{2}", " ", title)
return title.strip()
def generate_queries(title: str) -> list[str]:
"""Generate fake queries from article title."""
return [t.format(title=title) for t in QUERY_TEMPLATES]
def main():
parser = argparse.ArgumentParser(description="Generate fake training data")
parser.add_argument(
"--tiles-dir", type=Path, default=Path("/opt/dlami/nvme/kiwix_tiles")
)
parser.add_argument(
"--articles-json",
type=Path,
default=Path(
"/opt/dlami/nvme/kiwix/wikipedia_en_all_maxi_2025-08.zim.articles.json"
),
)
parser.add_argument("--output-dir", type=Path, default=Path("training/data"))
parser.add_argument("--num-articles", type=int, default=1000)
parser.add_argument("--seed", type=int, default=42)
args = parser.parse_args()
random.seed(args.seed)
args.output_dir.mkdir(parents=True, exist_ok=True)
logger.info("Loading articles.json...")
articles = json.loads(args.articles_json.read_text())
num_articles = len(articles)
logger.info(f"Loaded {num_articles} article slugs")
# Sample random article IDs and find ones with chunks
pairs = []
sampled = 0
indices = list(range(num_articles))
random.shuffle(indices)
for aid in indices:
if len(pairs) >= args.num_articles * 3: # 3 queries per article
break
sampled += 1
slug = articles[aid]
if not slug or slug.startswith("_"):
continue
chunk_paths = find_chunk_paths(args.tiles_dir, aid)
if not chunk_paths:
continue
title = title_from_slug(slug)
queries = generate_queries(title)
for query in queries:
# Pick a random chunk for this query
chunk_path = random.choice(chunk_paths)
pairs.append({"query": query, "chunk_path": chunk_path})
if len(pairs) % 300 == 0:
logger.info(
f"Generated {len(pairs)} pairs from {sampled} sampled articles..."
)
random.shuffle(pairs)
logger.info(f"Total pairs: {len(pairs)} from {sampled} sampled articles")
# Split 80/20
split = int(len(pairs) * 0.8)
train_pairs = pairs[:split]
eval_pairs = pairs[split:]
train_path = args.output_dir / "train.jsonl"
eval_path = args.output_dir / "eval.jsonl"
for path, data in [(train_path, train_pairs), (eval_path, eval_pairs)]:
with open(path, "w") as f:
for item in data:
f.write(json.dumps(item) + "\n")
logger.info(f"Wrote {len(data)} pairs to {path}")
logger.info("Done!")
if __name__ == "__main__":
main()