Text Classification
Transformers
Safetensors
Chinese
spam-detection
advertising-detection
ad-classification
chinese
modernbert
mmbert
multi-task
four-head
Instructions to use x6nux/laya-ad with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use x6nux/laya-ad with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="x6nux/laya-ad")# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("x6nux/laya-ad", device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 8,013 Bytes
e2492ec | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | #!/usr/bin/env python3
"""Merge Jev verdicts into the final gmeow ad corpus + stats/report.
Inputs : data/cleaned/gmeow/ads_jev.jsonl (one row per unique text)
Outputs: data/cleaned/gmeow/ads_final.jsonl label=ad, kind != none
data/cleaned/gmeow/rejected.jsonl Jev said clean / kind none
data/cleaned/gmeow/campaigns.jsonl near-dup clusters
data/cleaned/gmeow/stats.json
data/cleaned/gmeow/report.md
"""
from __future__ import annotations
import collections
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from clean_gmeow_ads import normalize, parse_source_msg, signature # noqa: E402
OUT = Path("data/cleaned/gmeow")
STORE = Path("data/raw/gmeow/bans.jsonl")
def load(p: Path) -> list[dict]:
if not p.exists():
return []
return [json.loads(l) for l in p.open(encoding="utf-8") if l.strip()]
def main() -> int:
rows = load(OUT / "ads_jev.jsonl")
latest: dict[str, dict] = {}
for r in rows: # last judgement wins if a text was re-judged
latest[r["text"]] = r
rows = list(latest.values())
ads = [r for r in rows if r["label"] == "ad" and r["kind"] != "none"]
rejected = [r for r in rows if r not in ads]
ads.sort(key=lambda r: (-r["n_accounts"], -r["n_records"], r["text"]))
# account/chat coverage measured on raw records, not summed per text
accounts: set[int] = set()
chats: set[int] = set()
ad_texts = {r["text"] for r in ads}
if STORE.exists():
for line in STORE.open(encoding="utf-8"):
try:
rec = json.loads(line)
except Exception: # noqa: BLE001
continue
_, msg = parse_source_msg(rec["info"])
if normalize(msg) in ad_texts:
accounts.add(rec["tgid"])
if rec["chat_id"] is not None:
chats.add(rec["chat_id"])
with (OUT / "ads_final.jsonl").open("w", encoding="utf-8") as fh:
for r in ads:
fh.write(json.dumps({
"text": r["text"],
"label": "ad",
"kind": r["kind"],
"scope": r["scope"],
"severity": r["severity"],
"conf": r["jev_conf"],
"quality": r["quality"],
"rule_kind": r["rule_kind"],
"n_records": r["n_records"],
"n_accounts": r["n_accounts"],
"n_chats": r["n_chats"],
"reason": r["reason"],
"channel": r["channel"],
"first_seen": r["first_seen"],
"last_seen": r["last_seen"],
}, ensure_ascii=False) + "\n")
with (OUT / "rejected.jsonl").open("w", encoding="utf-8") as fh:
for r in rejected:
fh.write(json.dumps({
"text": r["text"], "label": r["label"], "kind": r["kind"],
"reason": r["reason"], "n_records": r["n_records"],
"rule_kind": r["rule_kind"], "jev_conf": r["jev_conf"],
}, ensure_ascii=False) + "\n")
campaigns: dict[str, list[dict]] = collections.defaultdict(list)
for r in ads:
campaigns[signature(r["text"])].append(r)
camp_rows = []
for sig, members in campaigns.items():
members.sort(key=lambda r: -r["n_accounts"])
camp_rows.append({
"campaign": sig,
"variants": len(members),
"accounts": sum(m["n_accounts"] for m in members),
"kind": collections.Counter(m["kind"] for m in members).most_common(1)[0][0],
"canonical": members[0]["text"],
})
camp_rows.sort(key=lambda c: (-c["accounts"], -c["variants"]))
with (OUT / "campaigns.jsonl").open("w", encoding="utf-8") as fh:
for c in camp_rows:
fh.write(json.dumps(c, ensure_ascii=False) + "\n")
kind_dist = collections.Counter(r["kind"] for r in ads)
stats = {
"unique_texts_judged": len(rows),
"ads": len(ads),
"rejected": len(rejected),
"rejected_detail": dict(collections.Counter(
f"{r['label']}/{r['kind']}" for r in rejected)),
"kind_dist": dict(kind_dist),
"scope_dist": dict(collections.Counter(r["scope"] for r in ads)),
"severity_avg": round(sum(r["severity"] or 0 for r in ads) / max(1, len(ads)), 2),
"severity_hist": dict(collections.Counter(
round(r["severity"] or 0) for r in ads)),
"accounts_covered": len(accounts),
"chats_covered": len(chats),
"raw_records_covered": sum(r["n_records"] for r in ads),
"campaigns": len(camp_rows),
"rule_agreement": round(
sum(1 for r in ads if r["rule_kind"] == r["kind"]) / max(1, len(ads)), 3),
"quality_flags": dict(collections.Counter(
f for r in ads for f in r["quality"])),
"time_span": [min(r["first_seen"] for r in ads),
max(r["last_seen"] for r in ads)] if ads else [],
}
(OUT / "stats.json").write_text(
json.dumps(stats, ensure_ascii=False, indent=2), encoding="utf-8")
lines = [
"# gmeow 封禁广告语料 · 初次清理(Jev 实时判定)", "",
f"- 去重文本:{stats['unique_texts_judged']},Jev 判为广告 {stats['ads']},"
f"剔除 {stats['rejected']}({stats['rejected_detail']})",
f"- 覆盖封禁记录 {stats['raw_records_covered']} 条 / 账号 "
f"{stats['accounts_covered']} 个 / 时间跨度 {stats['time_span']}",
f"- 类别分布:{stats['kind_dist']}",
f"- 载体:{stats['scope_dist']};严重度均值 {stats['severity_avg']}"
f"(分布 {stats['severity_hist']})",
f"- 近重复战役簇 {stats['campaigns']} 个;规则与 Jev 一致率 "
f"{stats['rule_agreement']}",
"",
"## 每类样例(按覆盖账号数排序)", "",
]
for kind in ("scam", "promo", "porn", "crypto", "gambling", "porn_bait",
"spam_flood"):
top = [r for r in ads if r["kind"] == kind][:5]
if not top:
continue
lines += [f"### {kind}({kind_dist[kind]} 条)", ""]
for r in top:
text = r["text"].replace("\n", " ⏎ ")
lines.append(f"- `{r['n_accounts']}号` {text[:110]}")
lines.append("")
lines += ["## 最大战役簇", "", "| 账号数 | 变体 | 类别 | 代表文本 |", "|---:|---:|---|---|"]
for c in camp_rows[:10]:
text = c["canonical"].replace("|", "\\|").replace("\n", " ")
lines.append(f"| {c['accounts']} | {c['variants']} | {c['kind']} | {text[:70]} |")
lines += [
"",
"## 文件与复跑",
"",
"| 文件 | 内容 |",
"|---|---|",
"| `data/raw/gmeow/bans.jsonl` | 原始封禁记录(采集器增量去重写入) |",
"| `data/cleaned/gmeow/ads_jev.jsonl` | 每条唯一文本的 Jev 四问判定(实时追加) |",
"| `data/cleaned/gmeow/ads_final.jsonl` | 最终广告语料(label=ad 且类别非 none) |",
"| `data/cleaned/gmeow/rejected.jsonl` | Jev 判非广告/无类别的剔除项(含禁言次数,供复核) |",
"| `data/cleaned/gmeow/campaigns.jsonl` | 联系人/链接/数字归一后的近重复战役簇 |",
"",
"```bash",
"# 持续采集(页面只保留最近 100 条,约 4 条/分钟)",
"python scripts/collect_gmeow_bans.py --interval 45 --duration 7200",
"# 实时 Jev 清洗(新增文本自动判定,已判过的跳过)",
"python scripts/jev_clean_gmeow.py --watch --interval 20",
"# 重新生成最终语料与统计",
"python scripts/report_gmeow.py",
"```",
]
(OUT / "report.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
print(json.dumps(stats, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
|