laya-ad / code /scripts /build_optimized_dataset.py
x6nux's picture
laya-ad v2.2: six precision dirs
e2492ec verified
Raw History Blame Contribute Delete
8.27 kB
#!/usr/bin/env python3
"""构建优化训练集:teacher 清洗数据 + 之前 25k 数据合并,广告过采样 + 稀有 kind/severity 补齐。
- teacher 数据优先(同 text),早前数据补充;排除外部测试集文本,避免泄漏
- --gmeow 接入 gmeow 封禁广告语料(ads_final.jsonl,text/label/kind/scope/severity),
--gmeow_val_texts 指定留出 val 文本清单:留出行只进 val(并从 teacher/earlier 中剔除同文本防泄漏)
- normal 的 scope/severity 置 -100(交给二分类 gate),scope/severity 头只学广告
- train 广告过采样到 20%;kind 每类最少 1000;severity 0/1 最少 500/1000
- 重复样本用 marker_augment 生成标记形态变体,降低死记
- 输出 data/optimized_4h/{train,dev,val,holdout827}.jsonl + teacher_holdout/earlier_val
"""
import argparse
import json
import random
from collections import Counter
from pathlib import Path
import numpy as np
from sklearn.model_selection import train_test_split
from marker_augment import augment_markers
KINDS = ["crypto", "gambling", "porn", "porn_bait", "promo", "scam", "spam_flood"]
def load(p):
return [json.loads(l) for l in open(p, encoding="utf-8") if l.strip()]
def key(text: str) -> str:
return "".join(text.split()).lower()
def from_gmeow(r):
sev = r.get("severity")
return {
"text": r["text"],
"label_bin": 0,
"label_kind": KINDS.index(r["kind"]) if r.get("kind") in KINDS else -100,
"label_scope": (0 if r.get("scope") == "account" else 1) if r.get("scope") in ("account", "message") else -100,
"label_sev": int(round(sev)) if isinstance(sev, (int, float)) and 0 <= sev <= 3 else -100,
"source": "gmeow",
}
def from_teacher(r):
is_ad = r.get("label") == "ad"
kind = r.get("ad_kind")
return {
"text": r["text"],
"label_bin": 0 if is_ad else 1,
"label_kind": KINDS.index(kind) if (is_ad and kind in KINDS) else -100,
"label_scope": (0 if r.get("scope") == "account" else 1) if is_ad else -100,
"label_sev": (int(r.get("severity_bucket", -100))) if is_ad else -100,
"source": "teacher",
}
def from_earlier(r):
is_ad = r["label_bin"] == 0
return {
"text": r["text"],
"label_bin": r["label_bin"],
"label_kind": r["label_kind"] if is_ad else -100,
"label_scope": r["label_scope"] if is_ad else -100,
"label_sev": r["label_sev"] if is_ad else -100,
"source": "earlier",
}
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--teacher", default="data/cleaned/archive_latest_full_clean.jsonl")
ap.add_argument("--earlier_train", default="data/processed_4h/train.jsonl")
ap.add_argument("--external_teacher", default="data/processed_teacher/holdout827.jsonl")
ap.add_argument("--external_earlier", default="data/processed_4h/val.jsonl")
ap.add_argument("--out", default="data/optimized_4h")
ap.add_argument("--seed", type=int, default=42)
ap.add_argument("--ad_ratio", type=float, default=0.20)
ap.add_argument("--kind_min", type=int, default=1000)
ap.add_argument("--sev0_min", type=int, default=500)
ap.add_argument("--sev1_min", type=int, default=1000)
ap.add_argument("--gmeow", default=None,
help="gmeow ads_final.jsonl(text/label/kind/scope/severity)")
ap.add_argument("--gmeow_val_texts", default=None,
help="jsonl({text}):gmeow 留出 val 的文本清单")
args = ap.parse_args()
teacher = load(args.teacher)
earlier = load(args.earlier_train)
gmeow_hold, gmeow_in = [], []
if args.gmeow:
hold_keys = set()
if args.gmeow_val_texts:
hold_keys = {key(r["text"]) for r in load(args.gmeow_val_texts)}
gmeow = [from_gmeow(r) for r in load(args.gmeow)
if r.get("label") == "ad" and r.get("kind") in KINDS]
gmeow_hold = [m for m in gmeow if key(m["text"]) in hold_keys]
gmeow_in = [m for m in gmeow if key(m["text"]) not in hold_keys]
print(f"gmeow ads={len(gmeow)} train_pool={len(gmeow_in)} val_hold={len(gmeow_hold)}")
ext_t = [from_earlier(r) if "label_bin" in r else from_teacher(r) for r in load(args.external_teacher)]
ext_e = [from_earlier(r) if "label_bin" in r else from_teacher(r) for r in load(args.external_earlier)]
ext_texts = {r["text"] for r in ext_t + ext_e}
hold_keys = {key(m["text"]) for m in gmeow_hold}
combined = {}
for m in gmeow_in:
if m["text"] not in ext_texts:
combined[m["text"]] = m
for r in earlier:
m = from_earlier(r)
if m["text"] not in ext_texts and key(m["text"]) not in hold_keys:
combined[m["text"]] = m
for r in teacher:
m = from_teacher(r)
if m["text"] not in ext_texts and key(m["text"]) not in hold_keys:
combined[m["text"]] = m
rows = list(combined.values())
print("combined unique", len(rows), "ads", sum(1 for r in rows if r["label_bin"] == 0),
"kind", Counter(r["label_kind"] for r in rows if r["label_kind"] >= 0),
"sev", Counter(r["label_sev"] for r in rows if r["label_sev"] >= 0))
y = np.array([r["label_bin"] for r in rows])
idx = np.arange(len(rows))
tr, rest = train_test_split(idx, test_size=0.10, stratify=y, random_state=args.seed)
dev, rest2 = train_test_split(rest, test_size=0.50, stratify=y[rest], random_state=args.seed)
val, hold = train_test_split(rest2, test_size=0.50, stratify=y[rest2], random_state=args.seed)
splits = {"train": [rows[i] for i in tr], "dev": [rows[i] for i in dev],
"val": [rows[i] for i in val], "holdout827": [rows[i] for i in hold]}
splits["val"] = splits["val"] + gmeow_hold
if gmeow_hold:
print("gmeow val holdout", len(gmeow_hold),
"kind", Counter(KINDS[m["label_kind"]] for m in gmeow_hold if m["label_kind"] >= 0))
train = splits["train"]
rng = random.Random(args.seed)
normals = sum(1 for r in train if r["label_bin"] == 1)
ads = [r for r in train if r["label_bin"] == 0]
target_ads = int(args.ad_ratio / (1 - args.ad_ratio) * normals)
extra = max(0, target_ads - len(ads))
def dup(src, n):
out = []
for i in range(n):
r = dict(rng.choice(src))
r["text"] = augment_markers(r["text"], random.Random(args.seed * 1000 + i), p=0.5)
r["source"] = "oversample"
out.append(r)
return out
train += dup(ads, extra)
for k in range(len(KINDS)):
pool = [r for r in train if r["label_kind"] == k]
if pool and len(pool) < args.kind_min:
train += dup(pool, args.kind_min - len(pool))
for sev, minimum in ((0, args.sev0_min), (1, args.sev1_min)):
pool = [r for r in train if r["label_sev"] == sev]
if pool and len(pool) < minimum:
train += dup(pool, minimum - len(pool))
splits["train"] = train
out = Path(args.out)
out.mkdir(parents=True, exist_ok=True)
for name, data in splits.items():
with open(out / f"{name}.jsonl", "w", encoding="utf-8") as f:
for x in data:
f.write(json.dumps(x, ensure_ascii=False) + "\n")
print(name, len(data), "bin", Counter(x["label_bin"] for x in data),
"kind", Counter(x["label_kind"] for x in data if x["label_kind"] >= 0),
"scope", Counter(x["label_scope"] for x in data if x["label_scope"] >= 0),
"sev", Counter(x["label_sev"] for x in data if x["label_sev"] >= 0))
for name, data in (("teacher_holdout", ext_t), ("earlier_val", ext_e)):
with open(out / f"{name}.jsonl", "w", encoding="utf-8") as f:
for x in data:
f.write(json.dumps(x, ensure_ascii=False) + "\n")
print(name, len(data), "bin", Counter(x["label_bin"] for x in data),
"kind", Counter(x["label_kind"] for x in data if x["label_kind"] >= 0))
print("out", out.resolve())
if __name__ == "__main__":
main()