Zap / training /train /zapdata.py
ProCreations's picture
Zap v1: 20.7M-param web form & field classifier for autofill (bf16, MIT)
e4805d0 verified
Raw History Blame Contribute Delete
3.9 kB
"""Zap tokenization, windowing, label space and training-time augmentation."""
import random
from zap.extract import SPECIAL_TOKENS, serialize_ctx, serialize_field
FIELD_LABELS = ["username", "email", "current-password", "new-password", "one-time-code", "tel", "name",
"given-name", "family-name", "organization", "street-address", "address-line2", "address-level2",
"address-level1", "postal-code", "country", "cc-name", "cc-number", "cc-exp", "cc-exp-month",
"cc-exp-year", "cc-csc", "bday", "search", "captcha", "other"]
FORM_LABELS = ["login", "signup", "password-change", "password-recovery", "otp", "payment", "address", "newsletter",
"contact", "search", "other"]
# One token-classification label space: [CLS] carries form:*, each [FLD] carries field:*.
ID2LABEL = [f"form:{x}" for x in FORM_LABELS] + [f"field:{x}" for x in FIELD_LABELS]
LABEL2ID = {l: i for i, l in enumerate(ID2LABEL)}
N_FORM = len(FORM_LABELS)
MAX_LEN = 512
def windows(tok, ctx, fields, max_len=MAX_LEN):
"""Split a form into model inputs: each window = [CLS] ctx + as many whole fields as fit + [SEP].
Returns a list of (input_ids, field_positions, field_indices). Shared by training and inference;
the JS/extension port must do the same.
"""
ctx_ids = tok(serialize_ctx(ctx), add_special_tokens=False)["input_ids"][:160]
f_ids = [tok(serialize_field(f), add_special_tokens=False)["input_ids"][:120] for f in fields]
cls, sep = tok.cls_token_id, tok.sep_token_id
out, cur, cur_idx = [], [], []
budget = max_len - 2 - len(ctx_ids)
used = 0
for i, ids in enumerate(f_ids):
if cur and used + len(ids) > budget:
out.append((cur, cur_idx))
cur, cur_idx, used = [], [], 0
cur.append(ids)
cur_idx.append(i)
used += len(ids)
if cur or not out:
out.append((cur, cur_idx))
res = []
for chunk, idx in out:
ids = [cls] + ctx_ids
pos = []
for f in chunk:
pos.append(len(ids)) # f starts with the [FLD] token
ids += f
ids.append(sep)
res.append((ids, pos, idx))
return res
def augment(ctx, fields, rng: random.Random):
"""Hint dropout: teaches the model to cope with missing autocomplete/type/labels (e.g. revealed passwords)."""
ctx = dict(ctx)
for k, p in (("title", 0.1), ("url", 0.1), ("txt", 0.15), ("link", 0.1), ("hid", 0.2), ("head", 0.1)):
if rng.random() < p:
ctx[k] = ""
out = []
for f in fields:
f = dict(f)
if rng.random() < 0.25:
f["ac"] = ""
if f["type"] == "input password" and rng.random() < 0.08:
f["type"] = "input text"
for k, p in (("name", 0.05), ("id", 0.05), ("class", 0.1), ("lbl", 0.1), ("ph", 0.07), ("near", 0.1),
("after", 0.1), ("data", 0.1)):
if rng.random() < p:
f[k] = ""
out.append(f)
return ctx, out
def train_tokenizer(texts_iter, out_dir, vocab_size=16384):
import os
from tokenizers import BertWordPieceTokenizer
os.makedirs(out_dir, exist_ok=True)
t = BertWordPieceTokenizer(lowercase=True, strip_accents=False, clean_text=True, handle_chinese_chars=True)
t.train_from_iterator(texts_iter, vocab_size=vocab_size, min_frequency=3,
special_tokens=["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + SPECIAL_TOKENS,
limit_alphabet=6000, wordpieces_prefix="##")
from transformers import PreTrainedTokenizerFast
hf = PreTrainedTokenizerFast(tokenizer_object=t._tokenizer, unk_token="[UNK]", pad_token="[PAD]",
cls_token="[CLS]", sep_token="[SEP]", mask_token="[MASK]",
model_max_length=MAX_LEN)
hf.save_pretrained(out_dir)
return hf