laya-ad / code /scripts /build_kind_dataset.py
x6nux's picture
laya-ad v2.2: six precision dirs
e2492ec verified
Raw History Blame Contribute Delete
4.49 kB
#!/usr/bin/env python3
"""构建广告细类(7 类)数据集,复用 v1.1 的 train/val split,避免泄漏。
类别:crypto / porn / porn_bait / gambling / scam / promo / spam_flood
映射:contact → porn(样本均为色情招揽/迷药);spam / impersonate / unspecified 丢弃(样本极少且语义杂)
测试集:
gold_val = processed_v2/val ∩ gold.kind(广告行)
archive_test = processed_v2/val ∩ archive.strong(ad 行)
训练池:
processed_v2/train ∩ (gold.kind 广告行 ∪ archive.strong ad 行),85/15 分层切分 train/val
"""
import argparse
import collections
import json
from pathlib import Path
import numpy as np
from sklearn.model_selection import train_test_split
KINDS = ["crypto", "gambling", "porn", "porn_bait", "promo", "scam", "spam_flood"]
MAP = {"contact": "porn"}
DROP = {"spam", "impersonate", "unspecified", "normal", "none", None, ""}
def load(path):
return [json.loads(l) for l in open(path, encoding="utf-8") if l.strip()]
def norm(t):
return t.strip()
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--gold", default="data/raw/dataset/converted/gold.kind.jsonl")
ap.add_argument("--archive", default="data/raw/archive/converted/archive.strong.binary.jsonl")
ap.add_argument("--split_train", default="data/processed_v2/train.jsonl")
ap.add_argument("--split_val", default="data/processed_v2/val.jsonl")
ap.add_argument("--out", default="data/processed_kind")
ap.add_argument("--val_ratio", type=float, default=0.15)
ap.add_argument("--seed", type=int, default=42)
args = ap.parse_args()
def clean(lab):
if lab in MAP:
lab = MAP[lab]
return lab if lab in KINDS else None
gold_kind = {}
for r in load(args.gold):
lab = clean(r["label"])
if lab:
gold_kind.setdefault(norm(r["text"]), lab)
arch_kind = {}
for r in load(args.archive):
if r.get("label") != "ad":
continue
lab = clean(r.get("ad_kind"))
if lab:
arch_kind.setdefault(norm(r["text"]), lab)
tr_texts = {norm(r["text"]) for r in load(args.split_train)}
va_texts = {norm(r["text"]) for r in load(args.split_val)}
pool = ([{"text": t, "label": lab} for t, lab in gold_kind.items() if t in tr_texts] +
[{"text": t, "label": lab} for t, lab in arch_kind.items()
if t in tr_texts and t not in gold_kind])
gold_val = [{"text": t, "label": lab} for t, lab in gold_kind.items() if t in va_texts]
archive_test = [{"text": t, "label": lab} for t, lab in arch_kind.items()
if t in va_texts and t not in gold_kind]
assert not ({norm(r["text"]) for r in pool} & {norm(r["text"]) for r in gold_val})
assert not ({norm(r["text"]) for r in pool} & {norm(r["text"]) for r in archive_test})
labels = np.array([r["label"] for r in pool])
idx = np.arange(len(pool))
tr_idx, va_idx = train_test_split(idx, test_size=args.val_ratio,
stratify=labels, random_state=args.seed)
train = [pool[i] for i in tr_idx]
val = [pool[i] for i in va_idx]
out = Path(args.out)
out.mkdir(parents=True, exist_ok=True)
for name, rows in [("train", train), ("val", val),
("gold_val", gold_val), ("archive_test", archive_test)]:
with open(out / f"{name}.jsonl", "w", encoding="utf-8") as f:
for r in rows:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
print(f"{name:<14} n={len(rows):5d} {dict(collections.Counter(r['label'] for r in rows))}")
label_map = {k: i for i, k in enumerate(sorted(KINDS))}
(out / "label_map.json").write_text(
json.dumps(label_map, ensure_ascii=False, indent=2), encoding="utf-8")
(out / "report.json").write_text(json.dumps({
"kinds": KINDS, "label_map": label_map,
"train": len(train), "val": len(val),
"gold_val": len(gold_val), "archive_test": len(archive_test),
"train_dist": dict(collections.Counter(r["label"] for r in train)),
"val_dist": dict(collections.Counter(r["label"] for r in val)),
"gold_val_dist": dict(collections.Counter(r["label"] for r in gold_val)),
"archive_test_dist": dict(collections.Counter(r["label"] for r in archive_test)),
}, ensure_ascii=False, indent=2), encoding="utf-8")
print("输出:", out.resolve())
if __name__ == "__main__":
main()