laya-ad / code /scripts /convert_dataset.py
x6nux's picture
laya-ad v1.1:六精度权重 + 模型卡 + 项目代码快照
71dabc7 verified
Raw History Blame Contribute Delete
4.03 kB
#!/usr/bin/env python3
"""内部反广告数据集清洗转换 → 微调管线格式({"text","label"}+元数据)。
修正口径(依据数据集 README + 2026-10-24 实测体检):
1. verdict=ad 且 ad_kind=none/缺失 → 按生产兜底口径转 clean
2. 同文本多标注(gold 内实测 157 个矛盾文本)→ 按 quality 优先级取强标注
(llm_reviewed > manual > rule > hash > so_initial > other),平级取最新
3. kind="manual"(23 条,quality 值串入 kind 字段)→ 二分类下照常为 ad;
kind 方案下标为 unspecified
4. "类型: " 残留复验:实测 3 条均为正常用户内容(对话类型/消息类型等),非标签泄漏
5. 空文本剔除;保留 path/quality/ad_kind 元数据供分层采样
用法:
python scripts/convert_dataset.py --subset gold # 二分类(默认,生产网关口径)
python scripts/convert_dataset.py --subset gold --label-scheme kind # 细粒度 kind 类别
python scripts/convert_dataset.py --subset dedup --label-scheme kind
"""
import argparse
import gzip
import json
from collections import Counter
from pathlib import Path
QUALITY_RANK = {"llm_reviewed": 5, "manual": 4, "rule": 3, "hash": 2, "so_initial": 1, "other": 0}
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--src", default="data/raw/dataset/ft")
ap.add_argument("--subset", default="gold", choices=["gold", "dedup", "full"])
ap.add_argument("--label-scheme", default="binary", choices=["binary", "kind"],
help="binary: normal/ad(生产网关口径); kind: normal + 广告细类")
ap.add_argument("--out", default=None)
args = ap.parse_args()
src = Path(args.src) / f"antiad_ft_{args.subset}.jsonl.gz"
if not src.exists():
raise SystemExit(f"[ERR] 找不到 {src}")
out = args.out or f"data/raw/dataset/converted/{args.subset}.{args.label_scheme}.jsonl"
out = Path(out)
out.parent.mkdir(parents=True, exist_ok=True)
stat = Counter()
best = {} # text -> (quality_rank, time, row)
with gzip.open(src, "rt", encoding="utf-8") as f:
for line in f:
r = json.loads(line)
stat["读入"] += 1
text = (r.get("text") or "").strip()
if not text:
stat["丢弃:空文本"] += 1
continue
verdict, kind = r["verdict"], r.get("ad_kind")
if verdict == "ad":
if not kind or kind == "none":
stat["修正:ad+kind_none→clean"] += 1
verdict = "clean"
elif kind == "manual":
stat["修正:kind_manual→unspecified"] += 1
kind = "unspecified"
if verdict == "clean":
label = "normal"
elif args.label_scheme == "binary":
label = "ad"
else:
label = kind or "unspecified"
row = {"text": text, "label": label, "path": r["path"],
"quality": r["quality"], "ad_kind": kind, "id": r["id"]}
rank = QUALITY_RANK.get(r["quality"], 0)
prev = best.get(text)
if prev is None:
best[text] = (rank, r["time"], row)
else:
stat["去重:同文本取强标注"] += 1
if (rank, r["time"]) > (prev[0], prev[1]):
best[text] = (rank, r["time"], row)
rows = sorted((b[2] for b in best.values()), key=lambda x: x["id"])
with open(out, "w", encoding="utf-8") as f:
for row in rows:
f.write(json.dumps(row, ensure_ascii=False) + "\n")
labels = Counter(r["label"] for r in rows)
paths = Counter(r["path"] for r in rows)
print(f"[OK] {src.name} -> {out} ({len(rows)} 行)")
print("清洗统计:", dict(sorted(stat.items())))
print("label 分布:", dict(labels.most_common()))
print("path 分布:", dict(paths.most_common()))
if __name__ == "__main__":
main()