senpaicq's picture
Add LoRA adapter, pointer head, model card, code and results
a882958 verified
Raw History Blame Contribute Delete
9.25 kB
#!/usr/bin/env python3
"""Multimodal-Mind2Web hazırlığı: her adım için ilk-K aday listesi (görüntüsüz).
Çıktı (her split için bir jsonl.gz): adım başına görev, geçmiş eylemler, işlem türü,
adaylar (tag, kısa metin, kutu; K=0 ise hepsi, TF-IDF sırasıyla), doğru adayın sırası. Ekran görüntüleri BURADA
kaydedilmez; öznitelik çıkarırken akıştan tekrar okunur.
Buradaki sıralama yalnızca ön sıralama (TF-IDF). Gerçek ilk-50 süzgeci m2w_ranker.py'deki DeBERTa.
Gerçek bir satırda TF-IDF doğru öğeyi 554 adayın 349. sırasına koydu: tek başına yetmez.
Kullanım:
python m2w_prep.py --splits train --max_rows 300 --out m2w_smoke
python m2w_prep.py --splits train test_task test_website test_domain --out m2w_all
"""
import argparse
import gzip
import itertools
import json
import os
import re
import zlib
from collections import Counter
import numpy as np
from lxml import etree
from sklearn.feature_extraction.text import TfidfVectorizer
NAME = "osunlp/Multimodal-Mind2Web"
COLS = ["action_uid", "raw_html", "cleaned_html", "operation", "pos_candidates", "neg_candidates",
"website", "domain", "subdomain", "annotation_id", "confirmed_task",
"action_reprs", "target_action_index", "target_action_reprs"]
LABEL_ATTRS = ("aria_label", "placeholder", "title", "name", "alt", "value", "id")
# Yapısal alanlar (aday biçimi denemeleri için): metin öznitelikleri boş değilse, mantıksal öznitelikler varsa (boş değerle bile) saklanır
TEXT_AT = ("role", "type", "aria_label", "aria-label", "placeholder", "title", "name", "alt", "value", "id",
"aria_checked", "aria-checked", "aria_expanded", "aria-expanded", "aria_disabled", "aria-disabled",
"aria_selected", "aria-selected", "aria_haspopup", "aria-haspopup")
BOOL_AT = ("checked", "disabled", "readonly", "required", "selected")
ATTR_KEYS = Counter() # doğru adaylarda hangi öznitelik anahtarları görülüyor (özet için)
def small_attrs(a):
out = {}
for k in TEXT_AT:
v = a.get(k)
if v is not None and str(v).strip():
out[k] = str(v).strip()[:120]
for k in BOOL_AT:
if k in a:
out[k] = str(a[k])[:10]
return out
def _load(c):
return json.loads(c) if isinstance(c, str) else c
def parse_cand(c):
c = _load(c)
a = _load(c["attributes"]) if c.get("attributes") else {}
bbox = None
r = a.get("bounding_box_rect")
if r:
try:
v = [float(x) for x in (r.split(",") if isinstance(r, str) else r)]
if len(v) == 4:
bbox = v
except ValueError:
pass
return {"tag": c.get("tag", ""), "bid": str(c.get("backend_node_id", a.get("backend_node_id", ""))),
"attrs": a, "bbox": bbox, "orig": bool(c.get("is_original_target", False))}
def node_texts(cleaned_html, bids):
"""backend_node_id -> kısa iç metin (alt ağaçtan, ilk birkaç parça)."""
out = {}
if not cleaned_html or not bids:
return out
try:
root = etree.fromstring(cleaned_html.encode("utf-8", "ignore"),
etree.HTMLParser(recover=True, huge_tree=True))
except Exception:
return out
if root is None:
return out
for el in root.iter():
b = el.get("backend_node_id")
if b in bids:
t = " ".join("".join(itertools.islice(el.itertext(), 40)).split())
out[b] = t[:200]
return out
def cand_text(cd, inner):
a = cd["attrs"]
parts = [cd["tag"]]
for k in ("role", "type"):
if a.get(k):
parts.append(str(a[k]))
for k in LABEL_ATTRS:
v = a.get(k)
if v and str(v).strip():
parts.append(str(v).strip()[:60])
if inner:
parts.append(inner)
return " | ".join(parts)[:160]
def page_size(raw_html):
m = re.search(r'bounding_box_rect="([^"]+)"', raw_html[:4000])
if m:
v = m.group(1).split(",")
if len(v) == 4:
return float(v[2]), float(v[3])
return None, None
def rank(query, texts, bboxes, drop_zero_area):
"""Benzerlik puanı ile sıralı indeksler. Alanı sıfır olan öğeler en sona."""
n = len(texts)
if n == 0:
return []
try:
vec = TfidfVectorizer(token_pattern=r"[A-Za-z0-9]{2,}", lowercase=True, sublinear_tf=True)
M = vec.fit_transform(texts + [query])
sc = (M[:n] @ M[n].T).toarray().ravel()
except ValueError:
sc = np.zeros(n)
sc = sc.astype(float)
for i, b in enumerate(bboxes):
if b is None or b[2] <= 0 or b[3] <= 0:
sc[i] = -1.0 if drop_zero_area else sc[i] - 0.05
return list(np.argsort(-sc, kind="stable"))
def process_row(r, K, drop_zero_area):
pos = [parse_cand(c) for c in r["pos_candidates"]]
neg = [parse_cand(c) for c in r["neg_candidates"]]
allc = pos + neg
bids = {c["bid"] for c in allc}
texts_in = node_texts(r["cleaned_html"], bids)
texts = [cand_text(c, texts_in.get(c["bid"], "")[:100]) for c in allc] # eski metin: iç metin 100 karakter
idx = int(r["target_action_index"])
hist = list(r["action_reprs"][:idx])
query = r["confirmed_task"] + " " + " ".join(re.sub(r"->.*", "", h) for h in hist[-3:])
order = rank(query, texts, [c["bbox"] for c in allc], drop_zero_area)
if K:
order = order[:K]
pos_b = {c["bid"] for c in pos}
for c in pos:
ATTR_KEYS.update(c["attrs"].keys())
cands, pos_idx = [], []
for j, i in enumerate(order):
c = allc[i]
cands.append({"bid": c["bid"], "tag": c["tag"], "text": texts[i], "bbox": c["bbox"],
"inner": texts_in.get(c["bid"], ""), "at": small_attrs(c["attrs"])})
if c["bid"] in pos_b:
pos_idx.append(j)
pw, ph = page_size(r["raw_html"])
op = _load(r["operation"]) # gerçek veride JSON metni
return {
"action_uid": r["action_uid"], "ann": r["annotation_id"], "step": idx,
"website": r["website"], "domain": r["domain"], "task": r["confirmed_task"],
"history": hist, "target": r["target_action_reprs"],
"op": op["op"], "value": op.get("value", ""),
"page_w": pw, "page_h": ph, "n_total": len(allc), "n_pos": len(pos),
"cands": cands, "pos_idx": pos_idx,
}
def open_stream(split):
from datasets import load_dataset
try:
return load_dataset(NAME, split=split, streaming=True, columns=COLS)
except Exception as e: # columns desteklenmiyorsa
print("columns= çalışmadı, tüm kolonlarla akış:", type(e).__name__)
ds = load_dataset(NAME, split=split, streaming=True)
return ds.remove_columns([c for c in ("screenshot",) if c])
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--splits", nargs="+", default=["train"])
ap.add_argument("--out", default="m2w")
ap.add_argument("--K", type=int, default=0, help="0 = tüm adaylar")
ap.add_argument("--max_rows", type=int, default=0)
ap.add_argument("--val_mod", type=int, default=8, help="eğitim sitelerinin 1/val_mod kadarı doğrulamaya")
ap.add_argument("--keep_zero_area", action="store_true")
args = ap.parse_args()
os.makedirs(args.out, exist_ok=True)
for split in args.splits:
ds = open_stream(split)
if args.max_rows:
ds = ds.take(args.max_rows)
outs = {}
st = Counter()
n_cand = []
for k, r in enumerate(ds):
ex = process_row(r, args.K, not args.keep_zero_area)
name = split
if split == "train":
name = "val" if zlib.crc32(ex["website"].encode()) % args.val_mod == 0 else "train"
if name not in outs:
outs[name] = gzip.open(os.path.join(args.out, f"{name}.jsonl.gz"), "wt", encoding="utf-8")
ex["split"] = name
outs[name].write(json.dumps(ex, ensure_ascii=False) + "\n")
st[f"{name}/n"] += 1
st[f"{name}/op_{ex['op']}"] += 1
st[f"{name}/pos_yok"] += ex["n_pos"] == 0
st[f"{name}/pos_ilkK_icinde"] += len(ex["pos_idx"]) > 0
st[f"{name}/pos_ilk1"] += 0 in ex["pos_idx"]
n_cand.append(ex["n_total"])
if (k + 1) % 500 == 0:
print(f" {split}: {k + 1} adım", flush=True)
for f in outs.values():
f.close()
print(f"\n== {split} == adaylar/adım: ortanca {int(np.median(n_cand))}, en çok {max(n_cand)}")
for name in sorted({k.split('/')[0] for k in st}):
n = st[f"{name}/n"]
print(f" [{name}] adım={n} | doğru aday listede yok={st[name+'/pos_yok']} "
f"| recall@{args.K or 'hepsi'}={st[name+'/pos_ilkK_icinde']/n:.3f} | top1={st[name+'/pos_ilk1']/n:.3f} "
f"| CLICK={st[name+'/op_CLICK']} TYPE={st[name+'/op_TYPE']} SELECT={st[name+'/op_SELECT']}")
print("\ndoğru adaylarda görülen öznitelik anahtarları (ilk 40):", ATTR_KEYS.most_common(40))
json.dump(dict(ATTR_KEYS.most_common(200)), open(os.path.join(args.out, "oznitelik_anahtarlari.json"), "w"), indent=1)
print("\nyazıldı:", args.out)
if __name__ == "__main__":
main()